I want to zoom the google map after it exceeds it maximum zoom level. I google it and didn't find any solution. I am building a rooftop app any one can please help me on this.
The Google Maps API docs show that the google.maps.MapOptions object has a property called maxZoom that you can set when creating the map to set the maximum amount the map can be zoomed.
You can also set minZoom in the same way. So as long as your users don't need to be able to zoom very far out in order to move around effectively (e.g. if your app moves the map programmatically somehow) then you may even want to set the minZoom to a realistic value to ensure that people don't accidentally zoom too far out when trying to scroll down the page (I personally do that all the time with embedded google maps and it drives me crazy). But be careful with this: you don't want to ruin the user experience by removing your users' ability to navigate the map efficiently.
One thing you'll notice when reading the description of the maxZoom and minZoom parameters is that the docs don't specify the range of acceptable numbers. So how are you to figure out what numbers to pass in? And how do you change the max zoom after creating the map?
If the Map object is bound to a variable called map, then you can get the current zoom with map.getZoom() and you can set the current zoom with map.setZoom(zoom) where zoom is floating-point Number such that zoom >= 0. (Note: These methods get and set the "zoom" property of the Map object, but making changes directly to map.zoom will not cause the map to change, so be sure to use the getter and setter methods).
The most-zoomed-out level for the zoom property is 0, but the most-zoomed-in level will actually vary depending on where in the world the map is focused. (This makes sense, since street-level in some places could be a mile underground in others depending on elevation.) And at some zoom level, Google will no longer have (usable) satellite images.
Enter the MaxZoomService Class. This object's constructor can be found at google.maps.MaxZoomService. The instances of this class have only one useful method: getMaxZoomAtLatLng which takes a LatLng instance or a LatLngLiteral as its first parameter and a callback function to handle the result as its second.
A LatLng instance can be created with the google.maps.LatLng constructor, and a LatLngLiteral is just a normal object literal that has a lat property and a lng property. But since you'll probably be passing in a LatLng object from another method like map.getCenter(), you shouldn't have to worry about it.
Here's an example showing how to find out the maximum zoom available on the Satellite View at the center of the map's current view.
var mzs = new google.maps.MaxZoomService(); // Create a MaxZoomService instance
var mapCenterLatLng = map.getCenter(); // Get a LatLng instance
var handleMaxZoom = function(res) { // Callback Function accepting MaxZoomResult instance
if (res.status === "OK") {
// handle success
console.log("Max Zoom: " + res.zoom); // Print max possible zoom
map.setZoom(res.zoom); // Set the zoom to the max possible
} else {
// handle failure
console.log("Oops");
}
};
mzs.getMaxZoomAtLatLng(mapCenterLatLng, handleMaxZoom);
// => Max Zoom: [Number]
Edit:
If you really want to exceed the maximum zoom level (not just the default maximum zoom level as described above) your only real option is going to be using css transforms. Depending on how complicated the page layout is, this could end up being pretty difficult to get perfect.
I should note that this is typically not a good idea even if you can manage to get the layout to look good and the images to not be blurry. It carries all the same problems as using CSS to transform canvas elements (and some additional problems on top because it's not as simple as dealing with just a single DOM element). One notable problem is that applying CSS transforms will not affect the internal coordinates of the map (like those contained in click events) so you'll have to transform those every time you want to use them. Additionally, since the primary map div (the one returned by map.getDiv()) has to sit inside a container, this method will often cause it to overflow its container and you'll have to deal with that added complexity.
If you still want to do it, then here are some pieces of the puzzle that may help:
var mapCanvas = map.getDiv(); // The "main" div element that contains the map
var container = mapCanvas.parentElement; // You'll need some sort of container here
var scalingFactor = 1.5; // => Set a scaling factor (ex: 1.5 means scale it to 150% normal size)
/* Use CSS zoom property and/or vendor transforms to scale the map (though you could do this in straight CSS instead) */
mapCanvas.style.zoom = scalingFactor;
mapCanvas.style.MozTransform = 'scale(' + scalingFactor + ')';
mapCanvas.style.WebkitTransform = 'scale(' + scalingfactor + ')';
/* Then try to fit the scaled map back into its container, which will depend on how you're displaying it */
mapCanvas.style.width = "calc(100% / " + scalingFactor + ")"; // The CSS 'calc()' method might be useful here
Related
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.
I am testing Cesiumjs to see if it can reflect a near-real-time expreience - for example: position of airplanes.
For that, I need to draw billboards and make them move - which I know is possible with cesium, just not sure how.
The code looks like this:
var billboards = scene.primitives.add(new Cesium.BillboardCollection());
var billboard = {
image : '/path/to/logo.png',
position : Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883)
};
billboards.add(billboard);
My question is how do I change the position of the billboard. I couldn't find ant documentation that would explain.
I thought doing:
billboard.position = ... //new position
but how will cesium know that I've changed the position attribute unless it somehow turns that reference into a observable object.
So how do I update the location?
Thanks.
Cesium does indeed listen for changes to billboard.position
(source code here), so it is correct behavior for apps to simply write a new position.
Note that you must write the whole position at once, meaning you may not write to billboard.position.x. Instead, keep a "scratch" Cartesian3 around (don't create a new one every animation frame at 60fps), write to the x,y,z properties of your scratch variable, and then assign your scratch variable to billboard.position. You can see in the source that the assigned value will be cloned into another pre-existing Cartesian3, so you may immediately reuse the scratch variable.
Here's an example:
// Just once at app startup. Don't call "new" at 60fps.
var scratchCartesian3 = new Cesium.Cartesian3();
var ellipsoid = viewer.scene.mapProjection.ellipsoid;
function onTick() {
// This is safe to call at 60fps.
billboard.position = Cesium.Cartesian3.fromDegrees(
lon, lat, alt, ellipsoid, scratchCartesian3);
}
Also note that your question and the above answer are focused on the "Graphics Primitive" layer of the Cesium API. Cesium has one higher layer, called the "Entity" API, that you can use if you want Cesium to handle the concept of user-selectable objects with pop-up descriptions etc. Here's a Sandcastle demo showing how to add a billboard as a property of an entity, instead of as a primitive. This allows you to add other properties to the same entity, for example a name, description, label, 3D model, etc, and have them all be controlled from the same position property, and have Cesium take care of popup descriptions. The position property is more complex for entities than for primitives, for example it can be constant or sampled. This allows entities to change position over time when the timeline is shown.
I have a mapbox.js map but the parameter zoom doesn't seem to be doing anything
and I can't figure it out in the documentation. whatever I set zoom to the zoom level always defaults to my project zoom level Here is the code:
<script src='https://api.tiles.mapbox.com/mapbox.js/v1.6.1/mapbox.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox.js/v1.6.1/mapbox.css' rel='stylesheet' />
$(document).ready(function() {
var map = L.mapbox.map('map', 'nhaines.hek4pklk', {
zoom: 1
});
// disable drag and zoom handlers
map.dragging.disable();
map.touchZoom.disable();
map.doubleClickZoom.disable();
map.scrollWheelZoom.disable();
// disable tap handler, if present.
if (map.tap) map.tap.disable();
});
It took me a lot of digging around but I figured this out on my own. The dev's didn't respond yet. We were going about this all wrong. Here's what to do:
First, create the MapBox object, but set zoomAnimation to false. This question helped me realize that trying to setZoom while a CSS3 animation was in progress wouldn't ever work because it's tough to break out of. At least that's what I think is going on. Setting zoomAnimation to true allows the map to animate in and bypasses any custom zoom levels, so clearly this is very important.
var map = L.mapbox.map('map', 'username.map_id', {
zoomAnimation: false
});
Next, create a polygon layer and add to map from map's geojson. You find this within your MapBox projects > info tab. (In my case this geojson happens to contain the lat/lng coords of a polygon but your case may be different. featureLayer() should still add the geojson either way.)
var polygonLayer = L.mapbox.featureLayer().loadURL('http://your/maps/geojson').addTo(map);
After polygon layer (or whatever your lat/lng coords are of) has been added to map
polygonLayer.on('ready', function() {
// featureLayer.getBounds() returns the corners of the furthest-out markers,
// and map.fitBounds() makes sure that the map contains these.
map.fitBounds(polygonLayer.getBounds());
map.setView(map.getCenter(), 10);
}
Apparently fitBounds satisfies the map requirements to allow setView to be called on it, since now you can just call the map object directly to get the center lat/lng.
I haven't tested this in simple cases - I adapted this code from mine which checks if an address's lat/lng coords fall within a polygon while iterating over a series of MapBox maps. It should get you going though. Hope it helps!
I am using nokia.places.widget.SearchBox together with nokia.maps.map.Display.
You can use onSelect function of the SearchBox to perform operations when a user picks an items from the results list.
What I need to do is center and zoom the map to the picked place. My problem is concerning zooming because I don't know what level to use.
I noticed that some places have boundingBox (to use with the zoomTo function of the Display component) property but other doesn't. I could use category ('administrative-region', 'street-square', etc.) but it is other than precise and moreover I can't find a list of possible category codes.
Any suggestion?
To get the map to move/zoom to a location when a suggestion is chosen, you will need to implement an onResults handler:
var fromSearchBox = new nokia.places.widgets.SearchBox({
targetNode: "fromSearchBox",
template: "fromSearchBox",
map: map,
onResults: function (data) {
// Your code goes here //
}
});
As you noticed the data field may or may not hold a bounding box. This is because only a few locations cover a defined area, most are point addresses.
For the subset which have a boundingbox you can do the following:
if (data.results.items[0].boundingBox){
map.zoomTo(data.results.items[0].getBoundingBox());
}
For the remainder the answer will depend upon what you are trying to achieve, but you could try any of the following:
Move to the location on the map without altering the zoom. (i.e. let the user decide)
map.set("center", data.results.items[0].position);
Move to the specified bounding box for a point object at the
specified location.
var obj = new nokia.maps.map.StandardMarker(startPoint);
map.zoomTo(obj.getBoundingBox());
Or alternatively: map.zoomTo(nokia.maps.geo.BoundingBox.coverAll([startPoint]));
Define a bounding box surrounding the point location and zoom to that instead
startPoint =data.results.items[0].position;
bottomLeft = new nokia.maps.geo.Coordinate(startPoint.latitude - 0.1,
startPoint.longitude + 0.1);
topRight = new nokia.maps.geo.Coordinate(startPoint.latitude + 0.1,
startPoint.longitude - 0.1);
map.zoomTo(nokia.maps.geo.BoundingBox.coverAll([topRight, bottomLeft]));
Additionally, Display.zoomTo() can also take an additional parameter, so if you use map.zoomTo(BoundingBox, true) you will also keep the current center for the map on screen, and this may give more context to a user.
I am using the Google Maps v3 API. I currently am making a request to fetch new data each time a person changes the viewport (by either zooming or shifting the map) and am throwing away the old data I have. This works great, but now I want to cache the data so that I don't need to fetch the data each time the viewport changes. The Google Maps API defines a viewport by its Northeast and Southwest coordinate consisting of a latitude and a longitude. They are stored in objects called LatLngBounds.
I have come up with 2 ways I can do this:
Store the bounds of each new viewport the user visits and check if the new viewport is in an old viewport and fetch new data only of the part of the new viewport that is not within an old viewport. Essentially,
Divide each new viewport up into rectangular sections of data that we have and data that needs to be fetched. Store the bounds of each of the rectangular sections.
If anyone can think of a better way to do this, feel free to suggest new approaches.
My question is which one is going to be better in terms of better performance/memory usage and overall speed? They are both similar algorithms so does it really matter?
Also, right now both algorithms rely on dividing up the new viewport based on old viewports. What would the algorithm to divide new viewports look like? (Assume I implemented my 2nd algorithm)
var prevBounds = [ /* Array of previously seen bounds */ ];
var newViewport = map.getBounds(); // New Viewport to divide up
var sw = newViewport.getSouthWest();
var swlat = sw.lat();
var swlng = sw.lng();
var ne = newViewport.getNorthEast();
var nelat = ne.lat();
var nelng = ne.lng();
// newViewport.intersects(bounds)
// Returns true if this bounds shares any points with this bounds.
I might consider doing this more or less exactly the way Google serves up map tiles - instead of loading data for the entire viewport at once, carve up the entire map into square areas (though probably bigger areas than Google's 256x256 tiles), determine which areas are in the current viewport, and load data for those areas. As the user pans and zooms the map, check the viewport bounds to see whether new areas have come into the frame, and load them as necessary. Rough pseudocode:
var cache = {}
function onViewportChange() {
// get new bounds
var bounds = map.getBounds();
// identify all the areas in the bounds
var areas = getAreas(bounds);
areas.forEach(function(area) {
if (area.key in cache) {
// do nothing, or load items from cache
} else {
// load new data, storing the key (and maybe the data)
// to the cache
}
})
}
function getAreas(bounds) {
/* given a set of bounds, return an array of objects like:
[
{
x: 1,
y: 2,
zoom: 4,
key: "4-1,2",
bounds: b // lat/lon bounds of this area
},
...
]
*/
}
(See the Google explanation of map coordinates and this example for an idea of how to implement getAreas.)
The appeal of this is that the areas you're retrieving are much simpler, and it becomes very easy to check whether you've already loaded data for a given area - each area can have a simple unique key, probably a string made out of the x/y/zoom coordinates, and as each new area is loaded you save the key (or maybe the key and the data - it depends on whether you're removing old data from the map or just leaving it there) to some cache object. Then all you have to do when the viewport moves to a new area is check for the existence of that key in your cache.
The downside is that you might often load data outside the current viewport, and you're probably sending more requests to the server than you would with the implementations you suggest. This method might work best if you're serving up different data at different zoom levels - otherwise you might be stuck trying to choose a single-size cache area that works well at different zooms.