Maximum latitude map panning with leaflet - javascript

I am new to leaflet and I want the restrict the panning of a world map horizontally and not vertically (longitude but not latitude) because this map will display pictures when I click on them and I cant see well the image when i restrict the panning horizontally AND vertically. The map by itself it not a picture, it's a real world map. But when I click on certain location, a small picture will appear on the map.
I try to play with maxBounds and setMaxbounds. The normal maxBounds (to view the world map) is :
maxBounds: [[-85, -180.0],[85, 180.0]],
When i try to put the latitude to
[[-150, -180.0],[150, 180.0]]
, the vertical panning is still restricted. Can somebody help please? Thank you.

This sounds similar to a (quite obscure) issue in the Leaflet issue tracker a
while back: see https://github.com/Leaflet/Leaflet/issues/3081
However, that issue was dealing with infinite horizontal bounds, not vertical bounds in a CRS that already has some preset limits.
If you set the map's maxBounds to a value larger than 85 (the value for MAX_LATITUDE of L.Projection.Spherical) and run a debugger, the call stack goes through the map's _panInsideMapBounds(), then panInsideBounds(), then _limitCenter(), then _getBoundsOffset, then project(), then through the map CRS's latLngToPoint, then untimately L.Projection.Spherical's project(). L.Projection.Spherical.project() projects the bounds' limits into pixel coordinates, and clamps the projected point to be inside the projection's limits.
There are a lot of reasons behind this, one of them being to prevent users from putting markers outside the area covered with tiles:
(This is particularly important when a user confuses lat-lng with lng-lat and tries to use a value outside the [-90,90] range for latitude, and the projection code starts returning Infinity values everywhere)
How to get around this? Well, we can always specify the map's CRS, and we can create a CRS with a hacked projection which enforces a different limit. Please be aware that this changes how the pixelOrigin works internally (as explained in the Leaflet tutorial about extending layers), so stuff (particularly plugins) might break.
So something like:
var hackedSphericalMercator = L.Util.extend(L.Projection.SphericalMercator, {
MAX_LATITUDE: 89.999
});
var hackedEPSG3857 = L.Util.extend(L.CRS.EPSG3857, {
projection: hackedSphericalMercator
});
var map = new L.Map('mapcontainer', {
crs: hackedEPSG3857,
});
Of course, then you can set up your own maxBounds:
var map = new L.Map('mapcontainer', {
crs: hackedEPSG3857,
maxBounds: [[-Infinity, -10], [Infinity, 10]]
});
In this case, the bounds' limits would still be clamped to hackedSphericalMercator.MAX_LATITUDE, but you should have enough wiggle room for your application.
As a side note: A radically different approach to this problem would be to use a different map projection. We're used to a spherical cylindrical projection, but that's not the only way to flatten the earth.
In particular, a Transverse Mercator projection (or pretty much any other transverse cylindrical projection, for that matter) works pretty much in the same way, but wraps vertically instead of horizontally, and it's the projected longitudes, not latitudes, the ones which approach infinity asymptotically when approaching the [-180, 180] range. Let me borrow an image from its wikipedia article:
This implies a different set of challenges (namely finding some raster tiles appropriate for your application, including which prime meridian to use, and making proj4leaflet play nice), but it's definitely doable.

Related

GMap Bearing rotation in smooth motion (avoid jerky effect when changing bearing values)

I want to rotate GMap by changing the bearing angle value, so the camera rotates around the center point (360-Degree one full round ).
When we change the bearing, there is a easing effect at camera start and end points. How can I control/change that in order to make the rotation smooth when change Bearing values (in order to rotate map in 360 Degree, smooth animation)?
Required this for all languages as it appears the easing effect is different in different language libraries. e.g. Swift, Android, PHP, JS, Node.js, React.
Swift Example (running OK in Linear Animation):
Note that initially the animation did had jerks in iOS as well, but when we make use of CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear along its CATransaction properties then the GMap animation turned into smooth animation. so now if you see the code below, the change in Bearing value does not create jerky effect (due to the easing effect in GMap animation). I am looking for appropriate solution for Android and Web as well.
//Move the map around current location, first loop
let timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
CATransaction.begin()
CATransaction.setValue(3.0, forKey: kCATransactionAnimationDuration)
CATransaction.setAnimationTimingFunction(timingFunction)
CATransaction.setCompletionBlock({
//Move the map around current location, second loop
let timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
CATransaction.begin()
CATransaction.setValue(3.0, forKey: kCATransactionAnimationDuration)
CATransaction.setAnimationTimingFunction(timingFunction)
CATransaction.setCompletionBlock({
//Move the map around current location, third loop
let timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
CATransaction.begin()
CATransaction.setValue(3.0, forKey: kCATransactionAnimationDuration)
CATransaction.setAnimationTimingFunction(timingFunction)
CATransaction.setCompletionBlock({
UIView.animate(withDuration: 0.5, animations: {
self.findingYourLocation.alpha = 0.0
})
//TODO: Set nearest branch
// Zoom in one zoom level
let zoomCamera = GMSCameraUpdate.zoomIn()
self.mapView.animate(with: zoomCamera)
// Center the camera on UBL Branch when animation finished
//let nearestBranch = CLLocationCoordinate2D(latitude: 24.850751, longitude: 67.016589)
let nearestBranch = CLLocationCoordinate2D.init(latitude: 24.806849, longitude: 67.038734)
let nearestBranchCam = GMSCameraUpdate.setTarget(nearestBranch)
CATransaction.begin()
let timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
CATransaction.setValue(3.0, forKey: kCATransactionAnimationDuration)
CATransaction.setAnimationTimingFunction(timingFunction)
CATransaction.setCompletionBlock({
self.nextButton.alpha = 1.0
})
self.mapView.animate(with: nearestBranchCam)
self.mapView.animate(toZoom: 15)
self.mapView.animate(toBearing: 0)
self.mapView.animate(toViewingAngle: 0)
CATransaction.commit()
})
self.mapView.animate(toBearing: self.mapView.camera.bearing + 120)
CATransaction.commit()
})
self.mapView.animate(toBearing: self.mapView.camera.bearing + 120)
CATransaction.commit()
})
self.mapView.animate(toBearing: self.mapView.camera.bearing + 120)
CATransaction.commit()
The Android example code (has problem):
The Android example/sample code can be found here: https://issuetracker.google.com/issues/71738889
Which also includes an .apk file, an .mp4 video of sample app output. Which clearly shows jerky effects when Bearing value changes while rotating the map in 360-Degree.
Giving this as an answer as a comment would be rather hard to read; this is taken from the google documentation.
Consider this code:
CameraPosition cameraPosition = new CameraPosition.Builder()
.target(MOUNTAIN_VIEW) // Sets the center of the map to Mountain View
.zoom(17) // Sets the zoom
.bearing(90) // Sets the orientation of the camera to east
.tilt(30) // Sets the tilt of the camera to 30 degrees
.build(); // Creates a CameraPosition from the builder
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
This code creates a new camera position, and that's exactly what you're trying to mutate: the bearing of the camera. So if you create a new camera position like this:
CameraPosition cameraPosition = new CameraPosition.Builder()
.bearing(50)
.build();
and then animate the camera to that position:
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
That should do the trick. To give some more information on what you will need to use as bearing:
a bearing of 90 degrees results in a map where the upwards direction points due east.
Good luck.
I'm going to write this as another answer as I'd like to take the time to write a wall of text, whereas I'd like to keep the other answer as short as possible since it might still help other people with a similar problem.
The problem
So if I understand correctly, what you're trying to do is build an application with Google maps for different platforms. You're running into an issue with Google maps (the jerky movement) and you're trying to find a fix for all the platforms.
My proposed solutions
I'll divide this into a few sections, because I see different ways to go forward.
Find a solution for all the platforms.
This one seems like the most straightforward, but it could be akin to the XY problem. I've tried to introduce you to some ways of animating views, and you've solved the problem in your iOS app, but at the core what you're dealing with is a flaw in the Google maps animation when changing the bearing. I am not sure if there is a way to tackle this problem on every platform, as I haven't tried.
Use a different map
This sounds like a big step, and depending on your usage something you don't want to do. However, I've successfully used Leaflet (a JS map) with a WKWebView on iOS and Android and that all worked pretty well (this obviously also works fine in the browser). Keep in mind that some other maps might also have the jerky animation.
Wrapping it up
I hope I've given some insight. I'll add to this answer as we find out new things about the problem. Could you try to provide a minimal reproducible example? That way I can give it a better try. Good luck!
After going through all possible cases, I came to know that GMap is not built with required feature of Rotating map in 360-Degree in one go with custom animation. Don't know if this appears in next GMap api version.
For now, the possible solution is to change the animation logic, and instead of rotating 360-Degree we can rotate to e.g. 180-Degree with reduced animation speed (animation duration time).
This allows us to bring required map surrounding search experience to the user.
(For now I am posting this temporary answer, and allow others to update or post latest answer in future).
I have submitted this animation control issue on GMap issue tracker, please START this issue in order to show your interest and feedback, so Google team can take this feature into their final consideration.
https://issuetracker.google.com/u/1/issues/71738889
Here is my approach:
tilt = 45f
zoom = 18
target = currentLocation
bearing = lastKnownLocation.bearingTo(currentLocation)
Use map.animateCamera(); // instead of move camera
CameraPosition cameraPosition;
if (currentLocation.distanceTo(lastKnownLocation) > 50) {
cameraPosition = new CameraPosition.Builder()
.target(currentLatLng).zoom(20).tilt(45f).bearing(bearingValue).build();
} else {
cameraPosition = new CameraPosition.Builder()
.target(currentLatLng).zoom(20).tilt(45f).build();
}
map.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
What else you have to do?
You must call this method from LocationCallback (where you are getting location updates) and then we can have a smooth experience.
So if the user moved (walked or driving) and the distance between lastKnownLocation and currentLocation is greater than 50 meters then only we will set bearing otherwise we keep changing the targetLocation only.
In this way, we can show heading direction like in google maps.

How leaflet js load tiles from file system

suppose tiles are store in file system like below image
roughly seen this url http://build-failed.blogspot.in/2012/11/zoomable-image-with-leaflet.html
see the code
var map = L.map('map').setView([0, 0], 2);
L.tileLayer('http://localhost/tiles/eso/0/0/0.png', {
minZoom: 1,
maxZoom: 6,
attribution: 'Testing',
tms: true
}).addTo(map);
from the above code it seems that loading tile 0/0/0.png means refer one tile. so my question is how leaflet will load so many tiles because this url http://localhost/tiles/eso/0/0/0.png refer one tile.
the above code is right?
the above code can load bunch of tiles ?
what setView([0, 0], 2); doing ? what is the meaning of 0,0 and 2 what does it mean ?
also tell me what the below code is trying to say
var map = L.map('map').setView([0, 0], 2);
L.tileLayer('eso/{z}/{x}/{y}.jpg', {
minZoom: 1,
maxZoom: 6,
attribution: 'ESO/INAF-VST/OmegaCAM',
tms: true
}).addTo(map);
in the above code we do not mention any value for z or for y or x ?
what approach i need to follow in real life ?
do i need to specify value or leaf will provider value for z,y, and x at run time ?
please let me know. thanks
1) Above code is wrong. Test it and you will see always the same repeated 0/0/0 tile.
2) Above code cannot load bunch of tiles, or actually it creates bunch of tiles all having the same image.
3) setView([0, 0], 2) centers the map view on coordinates point latitude 0 degree, longitude 0 degree, and with zoom level 2 (zoom level 0 being the entire planet in 1 tile).
4) Refer to Leaflet documentation about raster Tile Layer:
{z} — zoom level, {x} and {y} — tile coordinates.
The first argument of L.tileLayeris the urlTemplate. The {z}, {x} and {y} texts are replaced by Leaflet by the target tile coordinates. For example, you can go to OpenStreetMap and right click on any tile (anywhere on the base map), select "Open image" (or "View image") and the browser will show only the particular tile with its exact URL, where z, x and y have been adjusted by Leaflet to point to that particular tile.
The background system is that tiles are arranged in a Quadtree structure, assuming zoom level 0 corresponds to the entire map fitting in a single tile (i.e. 0/0/0). Then for the next higher zoom level, each tile is subdivided into 4 child tiles. And so on.
So in the example folder structure you exhibit in your question, tiles 1/0/0 and 1/0/1 are the left half of tile 0/0/0. Tile 1/0/0 is the top left corner, tile 1/0/1 is the bottom left corner.
Again, all this is automatically managed by Leaflet, provided that your tiles follow this Quadtree arrangement (whether in folders or within their file name, like for Zoomify).
ghyb's answer for 1,2, and 3 are completely correct, you might want to vote that answer up.
...also tell me what the below code is trying to say
On your second question, ghybs is also completely correct - I will add to it from the perspective of someone who needed a simpler introduction. In addition to diving into the TileLayer code, you might start by googling 'slippy map' to get many intro-level conceptual overviews. A good and popular technical intro to slippy map tilenames and how to derive them from map coordinates, along with code examples in several languages, can be found in the openstreetmap documentation here. These will definitely help you wrap your head around the TileLayer code.
what approach i need to follow in real life ? do i need to specify
value or leaf will provider value for z,y, and x at run time ?
Leaflet and other libraries plug in the value of x,y,z as described by ghybs, based on the where you move and zoom on the map. They produce the values, and the request is made to the appropriate folders. In real life, you only need the folders with the right images (produced by a program that generates tiled images into those folder structures) - either on your computer, on a server - or, what is far more common, by using a web map server that delivers the images requested at x/y/z. Examples include geoserver, mapserver, mapnik, and many online services like google maps, openstreetmap, bing, etc.

Understanding Google Maps "fitBounds" method

I just want to clarify whether my way of understanding is correct. In my Google Maps app I would like to show to my users markers from particular continents.
Question: Am I right that a bound in Google Map API is an area made from NOT MORE AND NOT LESS than two points (markers)?
From math lessons, to draw 2D area I just need two points. Right? So to show to my users all markers from Europe should I just find two coordinates, one from Iceland (top, left) and second from let's say south-east Turkey? (For USA I would pick Seattle and Miami).
So, below JS code works perfectly. My question is - could you confirm that my way of understanding and the approach I've chosen is correct? Does it make any sense?
var bounds = new google.maps.LatLngBounds();
bounds.extend(new google.maps.LatLng('66.057878', '-22.579047')); // Iceland
bounds.extend(new google.maps.LatLng('37.961952', '43.878878')); // Turkey
this.map.fitBounds(bounds);
Yes, you are mostly correct. Except a 'bound' in Google maps case can include multiple points, for example if you had a point to the far right in the middle of the square in your image above and another at the bottom of the square in the middle you would still get an area the same as you have drawn above but with 3 points as in the edited map.
Obligatory link to the docs :)
https://developers.google.com/maps/documentation/javascript/reference?hl=en
You should not think about "top-left" and "bottom-right" but as south-west and north-east (so bottom-left and top-right if you like), as these are the coordinates used to create and/or retrieve the bounds object.
See the documentation and the getNorthEast and getSouthWest methods.
These two points are LatLng objects, and not markers. As an example, a marker is positioned on the map using a LatLng object.
I don't know your use case and your way of storing the data, but there might be a more precise way to display "Europe" markers. You could for example save the region along with each marker so that you can just query for "EU" markers and not others...

Floating point zoom and setZoom in Google Maps

The Google Maps documentation says that .zoom and setZoom() expect numbers. Everything would seem to point to these numbers being integers. However, today I gave the latter a floating point value and it worked ... sort of.
var MAP;
function initialize() {
MAP = new google.maps.Map(document.getElementById('map-canvas'),
{
zoom : 5,
center : new google.maps.LatLng(-25.610111, 134.354806),
mapTypeId : google.maps.MapTypeId.ROADMAP
});
}
Using the above code, I was able to have debugger access to the MAP object.
Now in the case of continental Australia, if I have a zoom of 4, I get a slice of South East Asia, all of Papua New Guinea and New Zealand and, in the centre, the Great Southern Land. If I have a zoom of 5 I get the mainland but without Tasmania -- definitely a "bad thing". The optimal zoom, discovered using MAP.setZoom() interactively, is 4.3. This is great, except that nothing else works with that zoom. When I try to draw polygons or a heatmap, Maps throws errors like
GET https://khms0.google.com/kh?v=178&hl=en-US&x=0&y=0&z=0.2999999999999998&token=77820 404 (OK)
I imagine that the 'z' value above is what's causing the 404 -- khms0.google.com is not expecting a floating point 'z' value.
So how do I get a more appropriately zoomed homeland, with polygons, markers, heatmaps, KML layers and what have you?
By the way, I did try using fitBounds() but it didn't change anything with respect to the visible contents of the viewport apart from shifting the map around a new centre.
var se = new google.maps.LatLng(-44.999315, 156.971343); // Tasman Sea
var nw = new google.maps.LatLng(-9.063496, 106.346343); // Indian Ocean
var bs = new google.maps.LatLngBounds(nw,se);
MAP.fitBounds(bs);
The zoom level must be an integer (at least at present). It used to be documented, but I don't see it stated specifically anywhere.
If a integer zoom level doesn't work for you, either change the size of the <div> displaying the map or make your own custom tiles that have a different scale.

OpenLayers minimal zoom smaller than bounding box

I've got a problem with osm and openlayers.
first of all, my map object
var wgs84 = new OpenLayers.Projection("EPSG:4326");
var map = new OpenLayers.Map ('map',
{
projection: new OpenLayers.Projection("EPSG:900913"),
displayProjection: wgs84,
maxExtent: new OpenLayers.Bounds(-180,-20, 180, 90),
controls: [
new OpenLayers.Control.MousePosition(),
new OpenLayers.Control.PanZoomBar(),
]
});
as you can see, my bounding box ends at -20, so the antarctis should be displayed.
but i'm able to zoom out so that i can see the whole world, and the map is smaller than the map-div (which is 900 * 500).
Is there any solution to fix it? you shouldn't be able to get a smaller map than the bounding box allows.
Have you implemented the restrictedExtent parameter?
(from the documentation)
restrictedExtent
{OpenLayers.Bounds} Limit map navigation to this extent where possible. If a non-null restrictedExtent is set, panning will be restricted to the given bounds. In addition, zooming to a resolution that displays more than the restricted extent will center the map on the restricted extent.
See an example here - This stops you panning beyond the bounds but does not restict the zoom levels. This seems to be a heavily reported missing feature that has not had many resolutions.
I believe this SO post has some workarounds for it thoughMin Max Zoom level in OpenLayers
Hope I have been some help.
Take a look at the sample I did at http://beta1234.com.sunflower.arvixe.com/maps/ (the server is a bit slow). It uses restrictedExtent and restricted zoom levels.

Categories