Limit Panning OpenLayers 5 - javascript

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.

Related

OpenLayers creating a complex style (polygon with a hole and a stroke on one side)

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.

How to resize Box , Open Layers 3?

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

leaflet.js ImageOverlay zoom changes marker position

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

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