leaflet.js ImageOverlay zoom changes marker position - javascript

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

Related

Limit Panning OpenLayers 5

I'm using OpenLayers 5 for my project and wasn't able to find anything on here for that. In the past, OpenLayers has had restrictExtent, but that seems to have disappeared. Is there a way to limit or restrict the user's ability to pan vertically so that they can't drag it off the screen?
I've tried using the maxExtent on both the layer containing the map and the View, and I've been able to limit what's visible by doing so on the View. I haven't been able to limit the panning, however. Any help with this would be awesome.
map.js
componentDidMount() {
BASE_VIEW.setMinZoom(this.getMinZoom());
this.resize();
this.map = new Map({
controls: defaultControls().extend([MOUSE_POSITION_CONTROL]),
layers: [BASE_LAYER],
target: 'map',
view: BASE_VIEW
});
}
map-constants.js
const baseView = new View({
center: fromLonLat(conus, projection),
projection: projection,
zoom: 4,
extent: [Number.NEGATIVE_INFINITY, -43, Number.POSITIVE_INFINITY, 43]
});
The nearest equivalent to OpenLayers 2 restrictedExtent in OL3/4/5 is the misleadingly named extent option of ol.View but that is a center constraint which restricts the map center, and depending on resolution and map size it is possible to pan the edges of the map considerably beyond that.
To replicate restrictedExtent you would need to recalculate the center constraint and reset the view as the resolution changes (ignoring very small changes to avoid performance issues). Assuming you have opened the map at the required maximum extent (using .fit() perhaps) you could then use this code
var extent = map.getView().calculateExtent(); // opening coverage
var resolution = 0;
function resChange() {
var newResolution = map.getView().getResolution();
if (resolution == 0 || Math.abs((newResolution-resolution)/resolution) > 0.01) {
resolution = newResolution;
var width = map.getSize()[0] * resolution;
var height = map.getSize()[1] * resolution;
var view = new ol.View({
projection: map.getView().getProjection(),
extent: [extent[0]+(width/2), extent[1]+(height/2), extent[2]-(width/2), extent[3]-(height/2)],
center: map.getView().getCenter(),
resolution: resolution,
maxZoom: map.getView().getMaxZoom(),
minZoom: map.getView().getMinZoom()
});
view.on('change:resolution', resChange);
map.setView(view);
}
}
resChange();
Here's a demo. For efficiency I'm only resetting the view at integer zoom levels (resulting in a stretch and snap back effect when zooming out close to the edges), and for the demo to work full page I reset the map when resized.
var view = new ol.View();
var map = new ol.Map({
layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ],
target: 'map'
});
function openMap() {
map.setView(view);
var extent = ol.proj.transformExtent([ -2, 50.5, 2, 53], 'EPSG:4326', 'EPSG:3857');
// fit the extent horizontally or vertically depending on screen size
// to determine the maximum resolution possible
var size = map.getSize();
var width = ol.extent.getWidth(extent);
var height = ol.extent.getHeight(extent);
if (size[0]/size[1] > width/height) {
view.fit([extent[0],(extent[1]+extent[3])/2,extent[2],(extent[1]+extent[3])/2], { constrainResolution: false });
} else {
view.fit([(extent[0]+extent[2])/2,extent[1],(extent[0]+extent[2])/2,extent[3]], { constrainResolution: false });
}
var maxResolution = view.getResolution();
var resolution = 0;
function resChange() {
var oldView = map.getView();
if (resolution == 0 || oldView.getZoom()%1 == 0) {
resolution = oldView.getResolution();
var width = map.getSize()[0] * resolution;
var height = map.getSize()[1] * resolution;
var newView = new ol.View({
projection: oldView.getProjection(),
extent: [extent[0]+(width/2), extent[1]+(height/2), extent[2]-(width/2), extent[3]-(height/2)],
resolution: resolution,
maxResolution: maxResolution,
rotation: oldView.getRotation()
});
newView.setCenter(newView.constrainCenter(oldView.getCenter()));
newView.on('change:resolution', resChange);
map.setView(newView);
}
}
resChange();
}
map.on('change:size', openMap);
openMap();
<link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" type="text/css">
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<div id="map" class="map"></div>
UPDATE
In OpenLayers 6 the view extent option does restrict the extent and not the center (useless constrainOnlyCenter is also specified) so no workaround is needed.

Leaflet custom url \ custom tiles

I am working on a custom map with leaflet. So far everything worked fine, but unfortunately the program I am using to split my image into tiles dont start the count with 0 but instead with 1, so my tiles start with "1_1.jpg" and so my whole map is shifted by one tile on y- and x-axis. To rename the tiles is not an option, cuz there to many, so I was looking for a possibility to change the {x} and {y} value in
L.tileLayer('images/map/{z}/C{x}_R{y}.jpg',
to something like x=x+1 and y=y+1, that would be my logic.
I've read that it would be possible with getTileUrl but I didn't understand how. I am still quite new to Javascript and this problem starts to drive me mad!
If anyone can help I would be very thankful.
Tile names are like "Cx_Ry.jpg" (for example first image "C1_R1.jpg") And here is the code.
var w = 16384, h = 16384; //Größe innerhalb Box
var map = L.map('image-map', {
minZoom: 0,
maxZoom: 5,
crs: L.CRS.Simple,
attributionControl: false,
}).setView([0, 0], 0);
var southWest = map.unproject([0, h], map.getMaxZoom());
var northEast = map.unproject([w, 0], map.getMaxZoom());
var bounds = new L.LatLngBounds(southWest, northEast);
map.setMaxBounds(bounds);
L.tileLayer('images/map/{z}/C{x}_R{y}.jpg', {
minZoom: 0,
maxZoom: 5,
tms: false,
continuousWorld: 'false',
noWrap: false,
defaultRadius:1,
}).addTo(map);
You can extend Leaflet's TileLayer class to provide your own getTileUrl method: http://leafletjs.com/examples/extending/extending-2-layers.html.
In this case, it would probably look something like this:
L.TileLayer.MyCustomLayer = L.TileLayer.extend({
getTileUrl: function(coords) {
// increment our x/y coords by 1 so they match our tile naming scheme
coords.x = coords.x + 1;
coords.y = coords.y + 1;
// pass the new coords on through the original getTileUrl
// see http://leafletjs.com/examples/extending/extending-1-classes.html
// for calling parent methods
return L.TileLayer.prototype.getTileUrl.call(this, coords);
}
});
// static factory as recommended by http://leafletjs.com/reference-1.0.3.html#class
L.tileLayer.myCustomLayer = function(templateUrl, options) {
return new L.TileLayer.MyCustomLayer(templateUrl, options);
}
// create the layer and add it to the map
L.tileLayer.myCustomLayer('images/map/{z}/C{x}_R{y}.jpg', {
minZoom: 0,
maxZoom: 5,
tms: false,
continuousWorld: 'false',
noWrap: false,
defaultRadius:1,
}).addTo(map);
Code is untested, but should get you moving in the right direction.

Set google map zoom dynamically

It's possible to define a level of zoom depending of the height of polygon shapes (administrative boundaries)?
This polygon going to be completely displayed into the map's div.
There is a map.fitBounds(my_bounds) function, which sets the viewport to contain the given bounds. It would set the map to the highest zoom level possible, in which you can see the whole bounds.
For that you need to determine the bounds of your polygon. If you use google.maps.Polygon to display your polygons, check out this solution.
In this case you would have:
google.maps.Polygon.prototype.getBounds = function() {
var bounds = new google.maps.LatLngBounds();
var paths = this.getPaths();
var path;
for (var i = 0; i < paths.getLength(); i++) {
path = paths.getAt(i);
for (var ii = 0; ii < path.getLength(); ii++) {
bounds.extend(path.getAt(ii));
}
}
return bounds;
}
...
map.fitBounds(my_polygon.getBounds());
If you use Data layer to display polygons, check out this SO answer. It provides example on how to calculate bounds of Data.Feature if it's a polygon.
If you have some other means of determining the bounds of polygon, then just use this:
map.fitBounds(new google.maps.LatLngBounds(new google.maps.LatLng(SouthWestCornerLat, SouthWestCornerLng), new google.maps.LatLng(NorthEastCornerLat, NorthEastCornerLng));

Tiling contiguous polygons in Google Maps

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

Changing icon based on zoom level

How do I go about changing an icon height & width based on the Google Maps zoom level?
I'm using Google Maps api v3.
This is the code I used eventually:
google.maps.event.addListener(google_map, 'zoom_changed', function() {
var z = google_map.getZoom();
_.each(map_shapes, function(s) {
if (! $.isFunction(s.shape.getPosition)) return;
var w = s.shape.getIcon().size.width;
var h = s.shape.getIcon().size.height;
s.shape.setIcon(new google.maps.MarkerImage(
s.shape.getIcon().url, null, null, null, new google.maps.Size(
w - Math.round(w / 3 * (last_zoom - z)),
h - Math.round(h / 3 * (last_zoom - z)))
)
);
});
last_zoom = z;
});
You should be able to add a listener on the zoom change per the docs. It doesnt get passed anything, but you can get the property via the api.
google.maps.event.addListener(map, 'zoom_changed', function() {
zoomLevel = map.getZoom();
//this is where you will do your icon height and width change.
});
This code will change the size of the icon every time the zoom level changes so the icon is the same geographic size.
//create a marker image with the path to your graphic and the size of your graphic
var markerImage = new google.maps.MarkerImage(
'myIcon.png',
new google.maps.Size(8,8), //size
null, //origin
null, //anchor
new google.maps.Size(8,8) //scale
);
var marker = new google.maps.Marker({
position: new google.maps.LatLng(38, -98),
map: map,
icon: markerImage //set the markers icon to the MarkerImage
});
//when the map zoom changes, resize the icon based on the zoom level so the marker covers the same geographic area
google.maps.event.addListener(map, 'zoom_changed', function() {
var pixelSizeAtZoom0 = 8; //the size of the icon at zoom level 0
var maxPixelSize = 350; //restricts the maximum size of the icon, otherwise the browser will choke at higher zoom levels trying to scale an image to millions of pixels
var zoom = map.getZoom();
var relativePixelSize = Math.round(pixelSizeAtZoom0*Math.pow(2,zoom)); // use 2 to the power of current zoom to calculate relative pixel size. Base of exponent is 2 because relative size should double every time you zoom in
if(relativePixelSize > maxPixelSize) //restrict the maximum size of the icon
relativePixelSize = maxPixelSize;
//change the size of the icon
marker.setIcon(
new google.maps.MarkerImage(
marker.getIcon().url, //marker's same icon graphic
null,//size
null,//origin
null, //anchor
new google.maps.Size(relativePixelSize, relativePixelSize) //changes the scale
)
);
});

Categories