iPhone : MapKit and displaying the current Map Scale - iphone

I need to display on the map a scale showing how far a inch / cm is for example. This will need to change depending on the zoom level.
My theory is that if I know the length of the map, and the length of the graphic, If I know what the current scale of the map was I could just do some maths to work out the graphic indicator scale.
So is there a way to get the current zoom lvl in meters? Is it linked to the span or something?

The zoom level is linked to the span - you first need to get the span of your map view, and then convert it into meters.

Related

How to change map color by current zoom level with mapbox gl js?

My question is about mapbox gl js.
How to change map fill-color by current zoom level?
The fill-color of this map has population growth rates by country, with a gradation from minimum to maximum. However, if the zoomed location has similar data, the fill-color difference becomes difficult to understand. So, I want to refer to the tile information according to the zoom level, get the country on the screen, and redraw it.
I searched a lot, but I couldn't figure out how to get the information on the screen. Please let me know if you have any information.
Zoom Level: 1
Zoom Level: 3
Thank you!
Your question initially asks:
How to change map color by current zoom level with mapbox gl js?
For that, you use an expression like 'fill-color': ['interpolate', ['linear'], ['zoom'], ...]
But what you want is something very different:
However, if the zoomed location has similar data, the fill-color difference becomes difficult to understand. So, I want to refer to the tile information according to the zoom level, get the country on the screen, and redraw it.
It sounds like what you want is context-sensitive color scaling. That is, instead of a fixed scale of colors where dark green always means X and light green means Y, instead, dark is the lowest value in the current viewport and light is the highest value in the viewport.
This does not have anything to do with zoom.
The steps you need are:
Detect when the viewport has changed: map.on("moveend", ...)
Find what values exist within the viewport: map.queryRenderedFeatures(...)
Calculate a new expression based on those values.
Set the new expression: map.setPaintProperty(...)

Displayed zoom level vs tile zoom level: pixel density?

Tiles come with a zoom level, and depending on the area that is viewed, leaflet fills the display with tiles of a certain zoom level.
Currently, the number of pixels in the display and the number of pixels in a tile, are tightly bound together, if I understand correctly. Or actually, it is probably the html/css pixels, which are no longer device pixels.
I believe that these are actually two fundamentally different zoom parameters, especially when (mobile) devices have varying pixel densities (window.devicePixelRatio).
My question is: is it possible to control which zoom level of the tiles is shown, as a function of the zoom level that is displayed (geospatial distance vs screen distance)?
The reason I ask is that the level of detail is often different for different zoom levels. On some devices displaying tiles of higher detail might actually look good. Some map sources, like topographic maps from http://geoportail.gouv.fr even change the map style drastically between different levels. I want to play with the possibility of showing, say, zoom level 15 over a large physical area on a hdpi display, where leaflet would normally show zoom level 14 or 13.
I found that by modifying the option "tileSize", passed to the TileLayer constructor, choosing a value lower than the default 256, I get almost what I want. However: the positioning is way off. Is there a simple solution for this?
After some digging in the source code, I noticed, as IvanSanchez pointed out, that the functionality is present indeed.
detectRetina applies a zoom of 'one up', that is bumping the zoom by one and dividing the length of the sides of the tiles by two, if the device has a devicePixelRatio >= 2.
I want to apply an arbitrary offset at will. This can be done at once for a layer by initializing with the options
let zoomOffset = 2;
let options = {
"detectRetina" : false,
"zoomOffset" : zoomOffset,
"tileSize" : 256 / Math.pow(2, zoomOffset)
}
However, it's even neater to have the possibility to do this realtime while viewing, so I wrote this L.Control-plugin: Leaflet.Control.DetailLevel
I want to play with the possibility of showing, say, zoom level 15 over a large physical area on a hdpi display, where leaflet would normally show zoom level 14 or 13.
It seems that what you want is already implemented by the detectRetina option of L.TileLayers. Quoting the docs::
If true and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.

Specifying Lat & Long for Leaflet TileLayer

Seems like a simple question, but I have been tearing my hair out for hours now.
I have a series of files ie.
kml_image_L1_0_0.jpg
kml_image_L2_0_0.jpg
kml_image_L2_0_1.jpg
kml_image_L2_1_0.jpg
kml_image_L2_1_1.jpg
etc. However just plotting them on the leaflet map surface understandibly puts the images at 0,0 on the earths surface, and the 0 zoom level inferred by the files should really be about 15 or so.
So I want to specify the latitude and longitude where the images should originate , and what zoom level they should start at. I have tried bounds (which doesn't display anything) and I have tried playing with offsetting the zoom level.
I need this because a user needs to click on an offline map to specify where they are and I need the GPS coordinates.
I also have a KML file but it seems to be of more help for plotting vector data on the map.
Any help is much appreciated, cheers.
If I understand correctly, the "kml_image_Lz_x_y.jpg" images that you have are actually tiles, with zoom, horizontal and vertical indices in their file name?
And your issue is that they use (z,x,y) numbers as if they started from the top-most level (zoom 0, single tile for entire world), but in fact they are just a small portion of the pyramid of tiles?
And you cannot use them as is because you still want to get actual geographic coordinates (latitude, longitude), which would be totally wrong if you used the tiles as if they were showing the entire world?
In that case, you have several options as workarounds:
The most simple and reliable would probably be to simply write a small script to rename all your tiles to their true (z,x,y) numbers.
Another option would be to modify the (z,x,y) numbers before they are written in the tile src attribute, and apply the appropriate offset (constant for z, scaled by z for x and y). That should probably happen in L.TileLayer.getTileUrl() method.
Good luck! :-)

Longitude, Latitude to XY coordinate conversion

I want to now how to convert longitude, latitude to its equivalent xy coordinate components in iPhone programming. (I am using only CoreLocation programming, and want to show a point on iPhone screen without any map).
thanks
Well the exact conversion depends on exactly which part of the Earth you want to show, and the stretching along longitude varies according to latitude, at least in Mercator.
That being said, even if you don't want to display an actual MapKit map, it would probably be easiest to create an MKMapView and keep it to one side. If you set the area you want to display appropriately on that (by setting the region property), you can use convertCoordinate:toPointToView: to map from longitude and latitude to a 2d screen location.
Note that MKMapView adjusts the region you set so as to make sense for the viewport its been given (eg, if you gave it a region that was a short fat rectangle, but the view it had was a tall thin rectangle, it'd pick the smallest region that covers the entire short fat rectangle but is the shape of a tall thin rectangle), so don't get confused if you specify a region with the top left being a particular geolocation, but then that geolocation isn't at the exact top left of the view.

MKMapView setRegion "snaps" to predefined zoom levels?

Can anyone confirm that setRegion "snaps" to predefined zoom levels and whether or not this behavior is as designed (although undocumented) or a known bug? Specifically, it appears that setRegion snaps to the same zoom levels that correspond to the zoom levels used when the user double-taps the map.
I'm trying to restore a previously saved region but this behavior makes it impossible if the saved region was set via a pinch zoom and not a double-tap zoom.
A big clue to me that things are broken on the mapkit side is what occurs if I call regionThatFits on the map's current region. It should return the same region (since it obviously fits the map's frame) but it returns the region that corresponds to the next higher predefined zoom level instead.
setVisibleMapRect behaves similarly.
Any further insight or information would be appreciated.
I found these related posts but neither included a solution or definitive confirmation that this is in fact a mapkit bug:
MKMapView setRegion: odd behavior?
MKMapView show incorrectly saved region
EDIT:
Here is an example that demonstrates the problem. All values are valid for my map view's aspect ratio:
MKCoordinateRegion initialRegion;
initialRegion.center.latitude = 47.700200f;
initialRegion.center.longitude = -122.367109f;
initialRegion.span.latitudeDelta = 0.065189f;
initialRegion.span.longitudeDelta = 0.067318f;
[map setRegion:initialRegion animated:NO];
NSLog(#"DEBUG initialRegion: %f %f %f %f", initialRegion.center.latitude, initialRegion.center.longitude, initialRegion.span.latitudeDelta, initialRegion.span.longitudeDelta);
NSLog(#"DEBUG map.region: %f %f %f %f", map.region.center.latitude, map.region.center.longitude, map.region.span.latitudeDelta, map.region.span.longitudeDelta);
OUTPUT:
DEBUG initialRegion: 47.700199 -122.367111 0.065189 0.067318
DEBUG map.region: 47.700289 -122.367096 0.106287 0.109863
Note the discrepancy in the latitude/longitude delta values. The map's values are almost double what I requested. The larger values correspond to one of the zoom levels used when the user double-taps the map.
Yes, it snaps to discrete levels. I've done quite a bit of experimentation, and it seems to like multiples of 2.68220906e-6 degrees of longitude per pixel.
So if your map fills the whole width of the screen, the first level spans .0008583 degrees, then the next level up you can get is twice that, .001717, and then the next one is twice that, .003433, and so on. I'm not sure why they chose to normalize by longitude, it means that fixes zoom levels vary depending on what part of the world you are looking at.
I've also spent a lot of time trying to understand the significance of that number .68220906e-6 degrees. It comes out to about 30cm at the equator, which kind of makes sense since the high resolution photos used by Google Maps have a 30cm resolution, but I would have expected them to use latitude instead of longitude to establish the zoom levels. That way, at maximum zoom, you always the native resolution of the satellite images, but who knows, they probably have some smart-people reason for making it work like that.
In my application I need to display a certain range of latitude. I'm gonna work on some code to try to zoom the map as close as possible to that. If anyone is interested, contact me.
I found a solution.
If the received snapped zoom level, is, lets say a factor of 1.2 bigger than the desired one:
use this algorithm to correct:
Asumption: you want to set the map view to exactly show "longitudinalMeters" from left to right
1) Calculate the correction scale:
Calculate the relation between longitudinal span you received, to that one you have got.
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(center, 0, longitudinalMeters);
MKCoordinateRegion regionFits = [mapView regionThatFits: region];
double correctionFactor = regionFits.span.longitudeDelta / region.span.longitudeDelta;
2) Create the transformation and apply it to the map
CGAffineTransform mapTransform = CGAffineTransformMakeScale(correctionScale, correctionScale);
CGAffineTransform pinTransform = CGAffineTransformInvert(mapTransform);
[mapView setTransform:mapTransform];
3) Apply the inverse transformation to the Map pins, to keep them at original size
[mapView setTransform:mapTransform];
for (id<MKAnnotation> annotation in self.mapView.annotations)
{
[[self.mapView viewForAnnotation:annotation] setTransform:pinTransform];
}
The weird behavior seems to be due to the fact that while one requests a particular region or view size, the actual API call to google is invoked with a center point and a zoom level. E.G.:
map.setCenter(new google.maps.LatLng(234.3453, 454.2345), 42);
Now it would be possible for Apple to request the appropriate zoom level and then adjust the sizing of the view to accommodate the actual region request, but it seems they fail to do so. I am drawing bus routes on a map, and one of my routes barely triggers a larger zoom level and thus scales too small (under-zooms) and looks ugly and smashed.
#pseudopeach, Please update me on the progress of your attempts to work around this issue. If one could detect the boundaries of a zoom level, the region request could then be deliberately underscaled to avoid the under-zoom. Since you are onto this I would be interested in seeing your code before I have to make an attempt at it myself.
There is an interesting category that the author of the blog Backspace Prolog has written to enable the direct manipulation of the Google Maps API by emulating their setCenter(centerPoint,ZoomLevel) call signature. You can find it here. I haven't spent the time yet, but the math can probably be reverse engineered to yield a means of calculating the zoom level for a given Region or MapRect. Depending on how far it is within the zoom level's range - i.e. how far it is over the threshold that triggers the lower zoom level - it could decide whether to go to the lower level or keep to higher one by under-requesting.
This is clearly a behavioral bug that needs to be fixed so that MKMapView can be used in a more refined manner.
This is an old question, but I recently investigated Google maps in detail, and can share some insight. I don't know whether this is also valid for the current Apple maps.
The reason that the resolution snaps to predefined zoomlevels is because the original maps fetched from Google's servers are drawn with those zoomlevels. The size of the features on those maps are drawn with a certain resolution in mind. For example, the width (in pixels) of a road on those maps is always the same. On higher resolution maps, more secundary roads are drawn, but their width is always the same. The resolution snaps to predefined levels to make sure those features are always depicted with the same size. That is, it is not a bug but a feature.
Those predefined resolutions vary with latitude because of the Mercator projection of the maps. Mercator projection is easy to work with because latitude lines are depicted straight and horizontal and longitude lines are straight and vertical. But with Mercator projection the top of the map has a slightly higher resolution than the bottom (on the Northern hemisphere). That has consequences for fitting maps together at the northern and sourthern edges.
That is, when you start on the equator and drive north, then the resolution of the Mercator maps you drive over will gradually increase. The longitude lines remain vertical, and therefore the longitude spans remains the same. But the resolution increases, and therefore the latitude span decreases. Still, on all those maps the roads have the same width in pixels, and texts are depicted in the same font size, etc.
Google uses a Mercator projection where the equator circumference is 256 pixels at zoomlevel 0. Each next zoomlevel doubles that amount. That is, at zoomlevel 1, the equator is 512 pixels long, at zoomlevel 2, the equator is 1024 pixels long, etc. The model for the earth they use is a FAI globe with a radius of exactly 6371 km, or circumference of 40030 km.
Therefore, resolution for zoomLevel 0 at the equator is 156.37 km/pixel, at zoomlevel 1 it is 78.19 km/pixel, etc. Those resolutions then vary with the cosinus of the latitude anywhere else on the earth.
MKCoordinateRegion region;
region.center.latitude = latitude;
region.center.longitude = longitude;
region.span.latitudeDelta = 5.0;
region.span.longitudeDelta = 5.0;
[mapView setRegion:region animated:YES];
I restore the region with no problem and with no variance as you describe. It is really impossible to tell what is specifically wrong in your case without some code to look at but here's what works for me:
Save both the center and span values somewhere. When you are restoring them specifically set both the center and span.
Restoring should look like this:
MKCoordinateRegion initialRegion;
initialRegion.center.latitude = Value you've stored
initialRegion.center.longitude = Value you've stored
initialRegion.span.latitudeDelta = Value you've stored
initialRegion.span.longitudeDelta = Value you've stored
[self.mapView setRegion:initialRegion animated:NO];
Also remember that this method is available in 4.0: `mapRectThatFits:edgePadding: MapRectThatFits helpfully adds a reasonable border to ensure that say a map annotation on the edge is not obscured and the the rect that you're attempting to display is fully visible. If you want to control the border use the call that gives you access to set edgePadding as well.
If you set up the MapView in InterfaceBuilder, make sure you don't do this:
_mapView = [[MKMapView alloc] init];
As soon as I removed this init line, my map view suddenly began responding properly to all the updates I sent it. I suspect that what happens is that if you do the alloc init, it's actually creating another view that's not being shown anywhere. The one you see on the screen is the one initialized by your nib. But if you alloc init a new one, then that's something somewhere else and it's not going to do anything.