I'm trying to draw a hexagonal grid in Google Maps. I've come up with a solution based off this answer which looks fine at higher zooms, but when zoomed further out I find that the classic "orange-peel" problem occurs: The hexagons no longer fit together like they should:
I'm using this rather cool geodesy library to calculate hexagon centers based on an ellipsoidal model (since a 2d model clearly doesn't work on a real-world map) but it's still looking pretty bad when zoomed out.
Preferably, I'd like to draw the hexagons in such a way that they are exactly the same shape and size on screen.
Here's the code I've been working with, also available as a Plunker here. I've tried calculating the vertices of each polygon using the same geodesy library that I'm using to calculate the polygon centers, but it still doesn't look right when zoomed out.
var hexgrid = [];
function initialize(){
// Create the map.
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 51.5, lng: 0},
scrollwheel: true,
zoom: 8
});
// This listener waits until the map is done zooming or panning,
// Then clears all existing polygons and re-draws them.
map.addListener('idle', function() {
// Figure out how big our grid needs to be
var spherical = google.maps.geometry.spherical,
bounds = map.getBounds(),
cor1 = bounds.getNorthEast(),
cor2 = bounds.getSouthWest(),
cor3 = new google.maps.LatLng(cor2.lat(), cor1.lng()),
cor4 = new google.maps.LatLng(cor1.lat(), cor2.lng()),
diagonal = spherical.computeDistanceBetween(cor1,cor2),
gridSize = diagonal / 20;
// Determine the actual distance between tiles
var d = 2 * gridSize * Math.cos(Math.PI / 6);
// Clear all the old tiles
hexgrid.forEach(function(hexagon){
hexagon.setMap(null);
});
hexgrid = [];
// Determine where the upper left-hand corner is.
bounds = map.getBounds();
ne = bounds.getNorthEast();
sw = bounds.getSouthWest();
var point = new LatLon(ne.lat(), sw.lng());
// ... Until we're at the bottom of the screen...
while(point.lat > sw.lat()){
// Keep this so that we know where to return to when we're done moving across to the right
leftPoint = new LatLon(point.lat, point.lon).destinationPoint(d, 150).destinationPoint(d, 210).destinationPoint(d, 270).destinationPoint(d, 90)
step = 1;
while(point.lon < ne.lng()){
// Use the modulus of step to determing if we want to angle up or down
if (step % 2 === 0){
point = new LatLon(point.lat, point.lon).destinationPoint(d, 30);
} else {
point = new LatLon(point.lat, point.lon).destinationPoint(d, 150);
}
step++; // Increment the step
// Draw the hexagon!
// First, come up with the corners.
vertices = [];
for(v = 1; v < 7; v++){
angle = v * 60;
vertex = point.destinationPoint(d / Math.sqrt(3), angle);
vertices.push({lat: vertex.lat, lng: vertex.lon});
}
// Create the shape
hexagon = new google.maps.Polygon({
map: map,
paths: vertices,
strokeColor: '#090',
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: '#090',
fillOpacity: 0.1,
draggable: false,
});
// Push it to hexgrid so we can delete it later
hexgrid.push(hexagon)
}
// Return to the left.
point = leftPoint;
}
});
}
google.maps.event.addDomListener(window, 'load', initialize);
Please consider that Google Maps is in Mercator Projection.
You have to compensate for the sphere of the globe on the projection.
https://en.wikipedia.org/wiki/Mercator_projection
Related
There was a difficulty in creating a complex polygon style.
The wording is as follows:
the polygon should be drawn as a polygon with a hole and a stroke on the outside.
In a difficult (as it seems to me) way, I made drawing a polygon with a hole:
convert to turf
using turf.buffer and a negative buffer value, I get an internal buffer
using turf.difference (source polygon and buffer) I get a polygon with a hole
But I don't understand how to draw the border only from the outside%)
If in the same function I try to return 2 styles (line + polygon), then I get an error (Uncaught TypeError: s.simplifyTransformed is not a function).
In general, is it possible to return 2 different geometries in the style?
In the picture the red polygon is what I need to get in the end.
Also I made a minimal example on codepen
I would be grateful for your help!
upd.
loops
and zoom out
To adapt the OpenLayers 3: Offset stroke style example for a polygon you would need to extend the ring by one segment at each end so you can correctly calculate the new coordinates at the original start/end point, then remove the excess when creating the resulting polygon.
var style = function(feature, resolution) {
var poly = feature.getGeometry();
if (poly.getType() == 'Polygon') {
var coordinates = poly.getCoordinates()[0];
coordinates = coordinates.slice(-2, -1).concat(coordinates).concat(coordinates.slice(1, 2));
var geom = new ol.geom.LineString(coordinates);
var colors = ['green', 'yellow', 'red'];
var width = 4;
var styles = [];
for (var line = 0; line < colors.length; line++) {
var dist = width * resolution * (line - (colors.length-1)/2);
var coords = [];
var counter = 0;
geom.forEachSegment(function(from, to) {
var angle = Math.atan2(to[1] - from[1], to[0] - from[0]);
var newFrom = [
Math.sin(angle) * dist + from[0],
-Math.cos(angle) * dist + from[1]
];
var newTo = [
Math.sin(angle) * dist + to[0],
-Math.cos(angle) * dist + to[1]
];
coords.push(newFrom);
coords.push(newTo);
if (coords.length > 2) {
var intersection = math.intersect(coords[counter], coords[counter+1], coords[counter+2], coords[counter+3]);
coords[counter+1] = (intersection) ? intersection : coords[counter+1];
coords[counter+2] = (intersection) ? intersection : coords[counter+2];
counter += 2;
}
});
styles.push(
new ol.style.Style({
geometry: new ol.geom.Polygon([coords.slice(2, -1)]),
stroke: new ol.style.Stroke({
color: colors[line],
width: width
})
})
);
}
return styles;
}
};
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var source = new ol.source.Vector();
var vector = new ol.layer.Vector({
source: source,
style: style
});
var map = new ol.Map({
layers: [raster, vector],
target: 'map',
view: new ol.View({
center: [-11000000, 4600000],
zoom: 4
})
});
map.addInteraction(new ol.interaction.Draw({
source: source,
type: 'Polygon',
style: style
}));
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/5.4.1/math.min.js"></script>
<div id="map" class="map"></div>
There is a problem with the original algorithm for LineStrings at corners with multiple vertices
When zoomed out the two vertices on the inner line should merge to a single point, but that is not happening, instead they cross and cause a kink in the line.
I am able to draw box and I can also move/drag box correctly.
But, how can I resize the box?
what exactly i need :
OpenLayers 2 Example,
https://harrywood.co.uk/maps/examples/openlayers/bbox-selector.view.html
Here is my code:
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var source = new ol.source.Vector({wrapX: false});
var vector = new ol.layer.Vector({
source: source
});
var map = new ol.Map({
layers: [raster, vector],
target: 'map',
view: new ol.View({
center: [-11000000, 4600000],
zoom: 4
})
});
var geometryFunction = ol.interaction.Draw.createBox();
box = new ol.interaction.Draw({
source: source,
type: 'Circle',
geometryFunction: geometryFunction
});
box.on('drawend', function (e) {
var bounds = e.feature.getGeometry().getExtent();
console.log(bounds);
});
map.addInteraction(box);
Code for select and drag/move box:
var select = new ol.interaction.Select();
var translate = new ol.interaction.Translate({
features: select.getFeatures()
});
translate.on('translateend', function (e) {
var bounds = e.features.getArray()[0].getGeometry().getExtent()
console.log(bounds);
});
map.addInteraction(select);
map.addInteraction(translate);
Elaboration to my comment:
You need to update/change the geometry of the "box" (polygon I suppose) to make it appear "resized", at the end of any operation that shows something on the map it uses extents that tell OL where to place things (essentially).
I have made a little example demonstrating how to use the .scale method on the Geometry object of a feature.
CodePen
Explanation:
draw.on("drawend", function(e){
var iterations = 0;
var interval = setInterval(function(){
if(iterations == 10){
clearInterval(interval);
}
iterations++;
var feature = e.feature;
var coords = feature.getGeometry();
coords.scale(0.9, 0.9);
}, 300)
This is the code I use to scale the drawn polygon when the polygon has been drawn on the map. I always scale it by 0.9 (that makes it smaller (Basic scale factoring)).
1 = The same size
0.* = Smaller
1.* = Bigger
You need to use similar logic to this above to resize your polygons. You need feature object, then extract the Geometry object, and use the .scale method.
The scale(sx, yx) method arguments are as follows:
sx = The scaling factor in the x-direction.
yx = The scaling factor in the y-direction (defaults to sx).
For more info Geometry Class in the OL docs
Am using ImageOverlay to use an image as a map, using Leaflet.js - but when changing the zoom the markers change position.
Have followed guideance in this tutorial and a code pen example is here.
// Markers
var markers = [{"title":"OneOcean Port Vell","description":"Super yacht marina","link":"http:\/\/www.oneoceanportvell.com\/","x":"68.28125","y":"68.443002780352178"}];
var map = L.map('image-map', {
minZoom: 2,
maxZoom: 4,
center: [0, 0],
zoom: 2,
attributionControl: false,
maxBoundsViscosity: 1.0,
crs: L.CRS.Simple
});
// dimensions of the image
var w = 3840,
h = 2159,
url = 'map.png';
// calculate the edges of the image, in coordinate space
var southWest = map.unproject([0, h], map.getMaxZoom()-1);
var northEast = map.unproject([w, 0], map.getMaxZoom()-1);
var bounds = new L.LatLngBounds(southWest, northEast);
// add the image overlay,
// so that it covers the entire map
L.imageOverlay(url, bounds).addTo(map);
// tell leaflet that the map is exactly as big as the image
map.setMaxBounds(bounds);
// Add Markers
for (var i = 0; i < markers.length; i++){
var marker = markers[i];
// Convert Percentage position to point
x = (marker['x']/100)*w;
y = (marker['y']/100)*h;
point = L.point((x / 2), (y / 2))
latlng = map.unproject(point);
L.marker(latlng).addTo(map);
}
In the codepen, change zoom to 4 to see the markers lose their position.
Ideally I'm trying to change the zoom to allow for different screen sizes and to get more of the map visible on mobile devices.
Also perhaps related, I can't see to get the zoomSnap feature to work to allow for fractional zooming.
Any pointers greatly appriciated.
map.unproject needs the zoom value at which you want the un-projection to be applied.
You correctly specify your static imageZoom value to unproject when computing your bounds and center, but not for your markers positions.
If the zoom parameter is not specified, then unproject uses the current map zoom level, i.e. what you have defined in your zoom variable. That is why when you change its value, unproject for your markers uses a different value, and they are positioned in different locations.
You even had to divide your x and y values (relative to your image) by 2, to account for the fact that in your initial situation, they are correct for an imageZoom of 4, but since you do not specify the zoom for unproject, the latter uses the current zoom (i.e. 3), so coordinates should be divided by 2 to be "correct".
Updated codepen: https://codepen.io/anon/pen/pLvbvv
On Leaflet I can create a new circle easily given the centre and the radius:
// Circle
var radius = 500; // [metres]
var circleLocation = new L.LatLng(centreLat, centreLon);
var circleOptions = {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5
};
var circle = new L.Circle(circleLocation, radius, circleOptions);
map.addLayer(circle);
The circle above is created and drawn without problems, so it is all.
However, if I wanted now to create and draw a rectangle that which bounds the circle, it does not work. Here is what I did:
// Rectangle
var halfside = radius; // It was 500 metres as reported above
// convert from latlng to a point (<-- I think the problem is here!)
var centre_point = map.latLngToContainerPoint([newCentreLat, newCentreLon]);
// Compute SouthWest and NorthEast points
var sw_point = L.point([centre_point.x - halfside, centre_point.y - halfside]);
var ne_point = L.point([centre_point.x + halfside, centre_point.y + halfside]);
// Convert the obtained points to latlng
var sw_LatLng = map.containerPointToLatLng(sw_point);
var ne_LatLng = map.containerPointToLatLng(ne_point);
// Create bound
var bounds = [sw_LatLng, ne_LatLng];
var rectangleOptions = {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5
};
var rectangle = L.rectangle(bounds, rectangleOptions);
map.addLayer(rectangle);
The size of the rectangle that I obtain has nothing to do with 500 metres. Also, it looks like the size of the rectangle depends on the zoom level the map is. None of these problems arose for the circle.
I suspect the way I transform the latitude/longitude to point and viceversa is wrong.
Just use the getBounds method that L.Circle inherits from L.Path:
Returns the LatLngBounds of the path.
http://leafletjs.com/reference.html#path-getbounds
var circle = new L.Circle([0,0], 500).addTo(map);
var rectangle = new L.Rectangle(circle.getBounds()).addTo(map);
Working example on Plunker: http://plnkr.co/edit/n55xLOIohNMY6sVA3GLT?p=preview
I was getting "Cannot read property 'layerPointToLatLng' of undefined" error, So I made some changes to iH8's answer.
var grp=L.featureGroup().addTo(map);
var circle=L.circle([0,0],{radius:<circle radius>}).addTo(grp);
L.rectangle(circle.getBounds()).addTo(this.bufferMap);
map.removeLayer(grp);
I wanted to make a map using openlayers but center it a unique way. For example I have a z/x/y coordinate of 12/2045/-1362, how do I convert it to longitude/latitude? It's quite the polar opposite of this: How to get X Y Z coordinates of tile by click on Leaflet map
It's quite hard for me to get the logic of the above link and invert it. I hope someone here has an experience or a ready-made formula for this. Thanks
Later I'll this in rendering the center of my map like this:
var z = 12;
var x = 2045;
var y = -1362;
function convertXYZtoCoor(z, x, y) {
// code here
return [lng, lat];
}
var coor = convertXYZtoCoor(z, x, y);
var view = new ol.View({
center: ol.proj.transform(
[coor[0], coor[1]], 'EPSG:4326', 'EPSG:3857'),
zoom: z
});
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
target: 'map',
view: view
});
Hope my question is understood more thanks.
Edit: Added code
var tileExtent = function(tileCoord){
var z = tileCoord[0];
var x = tileCoord[1];
var y = tileCoord[2];
var tileGridOrigin = tileGrid.getOrigin(z);
var tileSizeAtResolution = tileGrid.getTileSize(z) * tileGrid.getResolution(z);
return [
tileGridOrigin[0] + tileSizeAtResolution * x,
tileGridOrigin[1] + tileSizeAtResolution * y,
tileGridOrigin[0] + tileSizeAtResolution * (x + 1),
tileGridOrigin[1] + tileSizeAtResolution * (y + 1)
];
}
You can test/verify at http://jsfiddle.net/eurx57s7/
Note (stolen from the ol3 example, but it applies here to):
The tile coordinates are ol3 normalized tile coordinates (origin bottom left), not OSM tile coordinates (origin top left)