admin管理员组文章数量:1355522
I'm trying to display geospatial data in a hexagonal grid on a Google Map.
In order to do so, given a hexagon tile grid size X
I need to be able to convert ({lat, lng}
) coordinates into the ({lat, lng}
) centers of the hexagon grid tiles that contain them.
In the end, I would like to be able to display data on a Google Map like this:
Does anybody have any insight into how this is done?
I've tried porting this Python hexagon binning script, binner.py to Javascript but it doesn't seem to be working properly- the output values are all the same as the input ones.
For the sake of this example, I don't care if there are multiple polygons in a single location, I just need to figure out how to bin them into the correct coordinates.
Code below, (Plunker here!)
var map;
var pointCount = 0;
var locations = [];
var gridWidth = 200000; // hex tile size in meters
var bounds;
var places = [
[44.13, -69.51],
[45.23, -67.42],
[46.33, -66.53],
[44.43, -65.24],
[46.53, -64.15],
[44.63, -63.06],
[44.73, -62.17],
[43.83, -63.28],
[44.93, -64.39],
[44.13, -65.41],
[41.23, -66.52],
[44.33, -67.63],
[42.43, -68.74],
[44.53, -69.65],
[40.63, -70.97],
]
var SQRT3 = 1.73205080756887729352744634150587236;
$(document).ready(function(){
bounds = new google.maps.LatLngBounds();
map = new google.maps.Map(document.getElementById("map_canvas"), {center: {lat: 0, lng: 0}, zoom: 2});
// Adding a marker just so we can visualize where the actual data points are.
// In the end, we want to see the hex tile that contain them
places.forEach(function(place, p){
latlng = new google.maps.LatLng({lat: place[0], lng: place[1]});
marker = new google.maps.Marker({position: latlng, map: map})
// Fitting to bounds so the map is zoomed to the right place
bounds.extend(latlng);
});
map.fitBounds(bounds);
// Now, we draw our hexagons! (or try to)
locations = makeBins(places);
locations.forEach(function(place, p){
drawHorizontalHexagon(map, place, gridWidth);
})
});
function drawHorizontalHexagon(map,position,radius){
var coordinates = [];
for(var angle= 0;angle < 360; angle+=60) {
coordinates.push(google.maps.geometry.sphericalputeOffset(position, radius, angle));
}
// Construct the polygon.
var polygon = new google.maps.Polygon({
paths: coordinates,
position: position,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
geodesic: true
});
polygon.setMap(map);
}
// Below is my attempt at porting binner.py to Javascript.
// Source: .py
function distance(x1, y1, x2, y2){
console.log(x1, y1, x2, y2);
result = Math.sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
console.log("Distance: ", result);
return result;
}
function nearestCenterPoint(value, scale){
div = (value / (scale / 2));
mod = value % (scale / 2);
if(div % 2 == 1){
increment = 1;
} else {
increment = 0;
}
rounded = (scale / 2) * (div + increment);
if(div % 2 === 0){
increment = 1;
} else {
increment = 0;
}
rounded_scaled = (scale / 2) * (div + increment)
result = [rounded, rounded_scaled];
return result;
}
function makeBins(data){
bins = [];
data.forEach(function(place, p){
x = place[0];
y = place[1];
console.log("Original location:", x, y);
px_nearest = nearestCenterPoint(x, gridWidth);
py_nearest = nearestCenterPoint(y, gridWidth * SQRT3);
z1 = distance(x, y, px_nearest[0], py_nearest[0]);
z2 = distance(x, y, px_nearest[1], py_nearest[1]);
console.log(z1, z2);
if(z1 > z2){
bin = new google.maps.LatLng({lat: px_nearest[0], lng: py_nearest[0]});
console.log("Final location:", px_nearest[0], py_nearest[0]);
} else {
bin = new google.maps.LatLng({lat: px_nearest[1], lng: py_nearest[1]});
console.log("Final location:", px_nearest[1], py_nearest[1]);
}
bins.push(bin);
})
return bins;
}
I'm trying to display geospatial data in a hexagonal grid on a Google Map.
In order to do so, given a hexagon tile grid size X
I need to be able to convert ({lat, lng}
) coordinates into the ({lat, lng}
) centers of the hexagon grid tiles that contain them.
In the end, I would like to be able to display data on a Google Map like this:
Does anybody have any insight into how this is done?
I've tried porting this Python hexagon binning script, binner.py to Javascript but it doesn't seem to be working properly- the output values are all the same as the input ones.
For the sake of this example, I don't care if there are multiple polygons in a single location, I just need to figure out how to bin them into the correct coordinates.
Code below, (Plunker here!)
var map;
var pointCount = 0;
var locations = [];
var gridWidth = 200000; // hex tile size in meters
var bounds;
var places = [
[44.13, -69.51],
[45.23, -67.42],
[46.33, -66.53],
[44.43, -65.24],
[46.53, -64.15],
[44.63, -63.06],
[44.73, -62.17],
[43.83, -63.28],
[44.93, -64.39],
[44.13, -65.41],
[41.23, -66.52],
[44.33, -67.63],
[42.43, -68.74],
[44.53, -69.65],
[40.63, -70.97],
]
var SQRT3 = 1.73205080756887729352744634150587236;
$(document).ready(function(){
bounds = new google.maps.LatLngBounds();
map = new google.maps.Map(document.getElementById("map_canvas"), {center: {lat: 0, lng: 0}, zoom: 2});
// Adding a marker just so we can visualize where the actual data points are.
// In the end, we want to see the hex tile that contain them
places.forEach(function(place, p){
latlng = new google.maps.LatLng({lat: place[0], lng: place[1]});
marker = new google.maps.Marker({position: latlng, map: map})
// Fitting to bounds so the map is zoomed to the right place
bounds.extend(latlng);
});
map.fitBounds(bounds);
// Now, we draw our hexagons! (or try to)
locations = makeBins(places);
locations.forEach(function(place, p){
drawHorizontalHexagon(map, place, gridWidth);
})
});
function drawHorizontalHexagon(map,position,radius){
var coordinates = [];
for(var angle= 0;angle < 360; angle+=60) {
coordinates.push(google.maps.geometry.spherical.puteOffset(position, radius, angle));
}
// Construct the polygon.
var polygon = new google.maps.Polygon({
paths: coordinates,
position: position,
strokeColor: '#FF0000',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#FF0000',
fillOpacity: 0.35,
geodesic: true
});
polygon.setMap(map);
}
// Below is my attempt at porting binner.py to Javascript.
// Source: https://github./coryfoo/hexbins/blob/master/hexbin/binner.py
function distance(x1, y1, x2, y2){
console.log(x1, y1, x2, y2);
result = Math.sqrt(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)));
console.log("Distance: ", result);
return result;
}
function nearestCenterPoint(value, scale){
div = (value / (scale / 2));
mod = value % (scale / 2);
if(div % 2 == 1){
increment = 1;
} else {
increment = 0;
}
rounded = (scale / 2) * (div + increment);
if(div % 2 === 0){
increment = 1;
} else {
increment = 0;
}
rounded_scaled = (scale / 2) * (div + increment)
result = [rounded, rounded_scaled];
return result;
}
function makeBins(data){
bins = [];
data.forEach(function(place, p){
x = place[0];
y = place[1];
console.log("Original location:", x, y);
px_nearest = nearestCenterPoint(x, gridWidth);
py_nearest = nearestCenterPoint(y, gridWidth * SQRT3);
z1 = distance(x, y, px_nearest[0], py_nearest[0]);
z2 = distance(x, y, px_nearest[1], py_nearest[1]);
console.log(z1, z2);
if(z1 > z2){
bin = new google.maps.LatLng({lat: px_nearest[0], lng: py_nearest[0]});
console.log("Final location:", px_nearest[0], py_nearest[0]);
} else {
bin = new google.maps.LatLng({lat: px_nearest[1], lng: py_nearest[1]});
console.log("Final location:", px_nearest[1], py_nearest[1]);
}
bins.push(bin);
})
return bins;
}
Share
Improve this question
edited Feb 18, 2016 at 1:17
caitlin
asked Feb 17, 2016 at 21:51
caitlincaitlin
2,8294 gold badges31 silver badges65 bronze badges
7
- possible duplicate of How can I make a Google Maps API v3 hexagon tiled map, preferably coordinate-based? – geocodezip Commented Feb 17, 2016 at 21:58
- Similar question, but that question (and its answers) discuss how to draw the polygons, when I'm more interested in how to bin the data into the appropriate tile. – caitlin Commented Feb 17, 2016 at 22:19
- What should be done for the overlapping hexagons? Please provide a Minimal, Complete, Tested and Readable example in the question itself (a plunker or jsfiddle is useful, but should not be required to understand/answer the question). – geocodezip Commented Feb 17, 2016 at 22:21
- What are the data points that need to be binned into the hexagons? Do you want the random hexagons? Or do you want your random data binned into a hexagon grid (as you indicate in your title)? – geocodezip Commented Feb 17, 2016 at 22:40
- The random locations are just to create example data for the question: In a practical setting I would be binning real-world data into a hexagonal grid. Thanks for helping! – caitlin Commented Feb 17, 2016 at 22:49
2 Answers
Reset to default 5Use google.maps.geometry.poly.containsLocation.
for (var i = 0; i < hexgrid.length; i++) {
if (google.maps.geometry.poly.containsLocation(place, hexgrid[i])) {
if (!hexgrid[i].contains) {
hexgrid[i].contains = 0;
}
hexgrid[i].contains++
}
}
Example based off this related question: How can I make a Google Maps API v3 hexagon tiled map, preferably coordinate-based?. The number in the white box in the center of each hexagon is the number of markers contained by it.
proof of concept fiddle
code snippet:
var map = null;
var hexgrid = [];
function initMap() {
var myOptions = {
zoom: 8,
center: new google.maps.LatLng(43, -79.5),
mapTypeControl: true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU
},
navigationControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map(document.getElementById("map"),
myOptions);
createHexGrid();
var bounds = new google.maps.LatLngBounds();
// Seed our dataset with random locations
for (var i = 0; i < hexgrid.length; i++) {
var hexbounds = new google.maps.LatLngBounds();
for (var j = 0; j < hexgrid[i].getPath().getLength(); j++) {
bounds.extend(hexgrid[i].getPath().getAt(j));
hexbounds.extend(hexgrid[i].getPath().getAt(j));
}
hexgrid[i].bounds = hexbounds;
}
var span = bounds.toSpan();
var locations = [];
for (pointCount = 0; pointCount < 50; pointCount++) {
place = new google.maps.LatLng(Math.random() * span.lat() + bounds.getSouthWest().lat(), Math.random() * span.lng() + bounds.getSouthWest().lng());
bounds.extend(place);
locations.push(place);
var mark = new google.maps.Marker({
map: map,
position: place
});
// bin points in hexgrid
for (var i = 0; i < hexgrid.length; i++) {
if (google.maps.geometry.poly.containsLocation(place, hexgrid[i])) {
if (!hexgrid[i].contains) {
hexgrid[i].contains = 0;
}
hexgrid[i].contains++
}
}
}
// add labels
for (var i = 0; i < hexgrid.length; i++) {
if (typeof hexgrid[i].contains == 'undefined') {
hexgrid[i].contains = 0;
}
var labelText = "<div style='background-color:white'>" + hexgrid[i].contains + "</div>";
var myOptions = {
content: labelText,
boxStyle: {
border: "1px solid black",
textAlign: "center",
fontSize: "8pt",
width: "20px"
},
disableAutoPan: true,
pixelOffset: new google.maps.Size(-10, 0),
position: hexgrid[i].bounds.getCenter(),
closeBoxURL: "",
isHidden: false,
pane: "floatPane",
enableEventPropagation: true
};
var ibLabel = new InfoBox(myOptions);
ibLabel.open(map);
}
}
function createHexGrid() {
// === Hexagonal grid ===
var point = new google.maps.LatLng(42, -78.8);
map.setCenter(point);
var hex1 = google.maps.Polygon.RegularPoly(point, 25000, 6, 90, "#000000", 1, 1, "#00ff00", 0.5);
hex1.setMap(map);
var d = 2 * 25000 * Math.cos(Math.PI / 6);
hexgrid.push(hex1);
var hex30 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 30), 25000, 6, 90, "#000000", 1, 1, "#00ffff", 0.5);
hex30.setMap(map);
hexgrid.push(hex30);
var hex90 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 90), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex90.setMap(map);
hexgrid.push(hex90);
var hex150 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 150), 25000, 6, 90, "#000000", 1, 1, "#00ffff", 0.5);
hex150.setMap(map);
hexgrid.push(hex150);
var hex210 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 210), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex210.setMap(map);
hexgrid.push(hex210);
hex270 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 270), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex270.setMap(map);
hexgrid.push(hex270);
var hex330 = google.maps.Polygon.RegularPoly(EOffsetBearing(point, d, 330), 25000, 6, 90, "#000000", 1, 1, "#ffff00", 0.5);
hex330.setMap(map);
hexgrid.push(hex330);
var hex30_2 = google.maps.Polygon.RegularPoly(EOffsetBearing(EOffsetBearing(point, d, 30), d, 90), 25000, 6, 90, "#000000", 1, 1, "#ff0000", 0.5);
hex30_2.setMap(map);
hexgrid.push(hex30_2);
var hex150_2 = google.maps.Polygon.RegularPoly(EOffsetBearing(EOffsetBearing(point, d, 150), d, 90), 25000, 6, 90, "#000000", 1, 1, "#0000ff", 0.5);
hex150_2.setMap(map);
hexgrid.push(hex150_2);
var hex90_2 = google.maps.Polygon.RegularPoly(EOffsetBearing(EOffsetBearing(point, d, 90), d, 90), 25000, 6, 90, "#000000", 1, 1, "#00ff00", 0.5);
hex90_2.setMap(map);
hexgrid.push(hex90_2);
// This Javascript is based on code provided by the
// Community Church Javascript Team
// http://www.bisphamchurch.uk/
// http://econym.uk/gmap/
//]]>
}
google.maps.event.addDomListener(window, 'load', initMap);
// EShapes.js
//
// Based on an idea, and some lines of code, by "thetoy"
//
// This Javascript is provided by Mike Williams
// Community Church Javascript Team
// http://www.bisphamchurch.uk/
// http://econym.uk/gmap/
//
// This work is licenced under a Creative Commons Licence
// http://creativemons/licenses/by/2.0/uk/
//
// Version 0.0 04/Apr/2008 Not quite finished yet
// Version 1.0 10/Apr/2008 Initial release
// Version 3.0 12/Oct/2011 Ported to v3 by Lawrence Ross
google.maps.Polygon.Shape = function(point, r1, r2, r3, r4, rotation, vertexCount, strokeColour, strokeWeight, Strokepacity, fillColour, fillOpacity, opts, tilt) {
var rot = -rotation * Math.PI / 180;
var points = [];
var latConv = google.maps.geometry.spherical.puteDistanceBetween(point, new google.maps.LatLng(point.lat() + 0.1, point.lng())) * 10;
var lngConv = google.maps.geometry.spherical.puteDistanceBetween(point, new google.maps.LatLng(point.lat(), point.lng() + 0.1)) * 10;
var step = (360 / vertexCount) || 10;
var flop = -1;
if (tilt) {
var I1 = 180 / vertexCount;
} else {
var I1 = 0;
}
for (var i = I1; i <= 360.001 + I1; i += step) {
var r1a = flop ? r1 : r3;
var r2a = flop ? r2 : r4;
flop = -1 - flop;
var y = r1a * Math.cos(i * Math.PI / 180);
var x = r2a * Math.sin(i * Math.PI / 180);
var lng = (x * Math.cos(rot) - y * Math.sin(rot)) / lngConv;
var lat = (y * Math.cos(rot) + x * Math.sin(rot)) / latConv;
points.push(new google.maps.LatLng(point.lat() + lat, point.lng() + lng));
}
return (new google.maps.Polygon({
paths: points,
strokeColor: strokeColour,
strokeWeight: strokeWeight,
strokeOpacity: Strokepacity,
fillColor: fillColour,
fillOpacity: fillOpacity
}))
}
google.maps.Polygon.RegularPoly = function(point, radius, vertexCount, rotation, strokeColour, strokeWeight, Strokepacity, fillColour, fillOpacity, opts) {
rotation = rotation || 0;
var tilt = !(vertexCount & 1);
return google.maps.Polygon.Shape(point, radius, radius, radius, radius, rotation, vertexCount, strokeColour, strokeWeight, Strokepacity, fillColour, fillOpacity, opts, tilt)
}
function EOffsetBearing(point, dist, bearing) {
var latConv = google.maps.geometry.spherical.puteDistanceBetween(point, new google.maps.LatLng(point.lat() + 0.1, point.lng())) * 10;
var lngConv = google.maps.geometry.spherical.puteDistanceBetween(point, new google.maps.LatLng(point.lat(), point.lng() + 0.1)) * 10;
var lat = dist * Math.cos(bearing * Math.PI / 180) / latConv;
var lng = dist * Math.sin(bearing * Math.PI / 180) / lngConv;
return new google.maps.LatLng(point.lat() + lat, point.lng() + lng)
}
html,
body,
#map {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px
}
<script src="https://maps.googleapis./maps/api/js?libraries=geometry&key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<script src="https://google-maps-utility-library-v3.googlecode./svn/trunk/infobox/src/infobox.js"></script>
<div id="map"></div>
Take a look at deck.gl and its hexagon layer https://deck.gl/docs/api-reference/geo-layers/h3-hexagon-layer
With the getHexagon function, you can retrieve the H3 hexagon index of each object.
Then, you can use H3's cellToLatLng function to retrieve the hexagon cell's center coordinates.
But actually, you won't need this step if you just want to create a hexagonal data grid like the one you have shown in the image. You can simply feed the data into the deck.gl hexagon layer and the framework will do the rest to produce the output you want.
本文标签: javascriptBinning data into a hexagonal grid in Google MapsStack Overflow
版权声明:本文标题:javascript - Binning data into a hexagonal grid in Google Maps - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744007612a2574984.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论