0
点赞
收藏
分享

微信扫一扫

MKMapView 缩放等级 原理详解


MKMapView and Zoom Levels: A Visual Guide

http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/


So, how exactly does the code provided in the previous post work? What follows is a visual explanation of Google Maps, zoom levels, and how you go about adding support for zoom levels to the MKMapView

Round to Flat

This is planet Earth:

As you may know, it is round.

To create a map of the Earth, the curved surface must be projected onto a flat surface. There are many map projections that attempt to flatten the Earth. There are distortions inherent to every projection, but each map projection aims to preserve at least one quality from the original curved representation.

Some projections preserve area, such as the Mollweide projection:

Equirectangluar projections preserve distance between the meridians:

The Mercator projection stretches out the poles in order to preserve locally measured angles:

Google uses the Mercator projection to render Google Maps:

Mercator Math

The Mercator projection converts latitude (φ) and longitude (λ) coordinates to pixel values. It uses math:

MKMapView 缩放等级 原理详解_Google

You don’t have to understand the math; just know that it converts latitudes and longitudes to pixels.

But, where are these pixels? Well, it depends on your zoom level.

Zoom Levels

At zoom level 0, Google displays the world in a single 256 pixel by 256 pixel tile:

At zoom level 1, Google doubles the area of the map while keeping the tile size constant. So, the map grows to 512 pixels by 512 pixels and uses four tiles:

At zoom level 2, Google doubles the area again. The map grows to 1024 pixels by 1024 pixels and uses sixteen tiles:

The pixel area continues to double at each zoom level, and when zoom level 20 is reached, the map is 536,870,912 pixels by 536,870,912 pixels. It has so many tiles we won’t bother to count them:

Latitudes and Longitudes to Pixels

As part of the PHP Static Maps project, Mike Tuupola wrote some code that converts latitudes and longitudes to pixels at zoom level 20. The code is easily ported to Objective-C:



// Convert latitude and longitude to pixel values at zoom level 20

#define MERCATOR_OFFSET 268435456 /* (total pixels at zoom level 20) / 2 */
#define MERCATOR_RADIUS 85445659.44705395 /* MERCATOR_OFFSET / pi */

round(MERCATOR_OFFSET + MERCATOR_RADIUS * longitude * M_PI / 180.0);
y = round(MERCATOR_OFFSET - MERCATOR_RADIUS * logf((1 + sinf(latitude * M_PI / 180.0)) / (1 - sinf(latitude * M_PI / 180.0))) / 2.0);



does

Add an iPhone

Say we place an iPhone on top of Anchorage, Alaska at zoom level 20:

In the iPhone shown above, the map size is 320 pixels by 460 pixels. Since we know the map dimensions and center coordinate in pixels, we can easily compute the pixel coordinates of the top-left corner relative to the center pixel coordinate:

MKMapView 缩放等级 原理详解_ide_02

We can find the relative position of the top-right and bottom-left pixel coordinates as well:

MKMapView 缩放等级 原理详解_ide_03

The PHP Static Maps code also provides code to go from pixels at zoom level 20 to latitudes and longitudes:


// Convert pixel values at zoom level 20 to latitude and longitude 
   
 
M_PI / 2.0 - 2.0 * atan(exp((round(pixelY) - MERCATOR_OFFSET) / MERCATOR_RADIUS))) * 180.0 / M_PI;
 longitude = ((round(pixelX) - MERCATOR_OFFSET) / MERCATOR_RADIUS) * 180.0 / M_PI;


We can use this code to convert the corners from pixel coordinates to latitudes and longitudes:

MKMapView 缩放等级 原理详解_ide_04

MKCoordinateSpan. That span, in turn, is used to initialize the region property of an MKMapView:


// Create an MKCoordinateSpan to initialize the map’s region 
   
 
MKCoordinateSpan span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta);
MKCoordinateRegion region = MKCoordinateRegionMake(centerCoordinate, span);
 [mapView setRegion:region animated:NO];



And you’re done!…That is, if you want to see zoom level 20. What do you do when your user wants to see the map at zoom level 19 instead of 20?

Scaling using Zoom Levels

Relative to zoom level 20, zooming out one level doubles the area visible on the map.

For example, consider the image below. On the left is Anchorage at zoom level 19, and on the right are the 4 iPhones at zoom level 20 it would take to display the same amount of area:

MKMapView 缩放等级 原理详解_ide_05

If we move up another level, the area doubles again. Consider the following image. On the left is Anchorage at zoom level 18, and on the right are the 16 iPhones at zoom level 20 it would take to display the same amount of area:

MKMapView 缩放等级 原理详解_Google_06

Since the area doubles at each zoom level, we can define the following exponential relationship between the zoom level and the area covered by the map:


// Compute a scaling factor that will take us from any zoom level to zoom level 20 
   
 
NSInteger zoomExponent = 20 - zoomLevel;
double zoomScale = pow(2, zoomExponent);double scaledMapWidth = mapSizeInPixels.width * zoomScale;
double scaledMapHeight = mapSizeInPixels.height * zoomScale;


For instance, here is Anchorage at zoom levels 20, 19, and 18. The map’s width and height in pixels are unaltered:

MKMapView 缩放等级 原理详解_Google_07

After computing the zoom scale factor, we can apply it to each map to determine its dimensions at zoom level 20:

MKMapView 缩放等级 原理详解_git_08

After we compute these new dimensions, we plug them into the algorithm for finding the coordinates of the map corners.

An Example: Zoom Level 18

For instance, say we take the map at zoom level 18:

MKMapView 缩放等级 原理详解_ide_09

Let’s drop the matrix of phones but keep the scaled width and height:

MKMapView 缩放等级 原理详解_git_10

We find the top-left corner just like we did before, except now we use the scaled width and height:

MKMapView 缩放等级 原理详解_ide_11

Similarly, we use the scaled width and height for finding the top-right and bottom-left corners as well:

MKMapView 缩放等级 原理详解_git_12

Using the pixel and latitude and pixel and longitude helper methods, we can compute the coordinates of the corners and the distance between them:

MKMapView 缩放等级 原理详解_ide_13

These delta values are used to initialize the map’s region property, and the map zooms to the level you specify.

That’s a Wrap

Be sure to check out the previous post for the full code that adds support for zoom levels toMKMapView.

If you are interested in learning more from someone much smarter than I am, check out these posts from Charlie Savage, a programmer and cartographer extraordinaire:

  • Google Maps Deconstructed
  • Google Maps Revisted
  • Mouse Coordinates to Lat/Long

Much of what I know about maps is from these articles, and I highly recommended checking them out if you want to learn more about how Google Maps works under the hood.



Posted in iPhone DevelopmentTagged mapkit, maps


29 Responses to “MKMapView and Zoom Levels: A Visual Guide”

  1. Kai Renz March 24, 2010 at 1:24 pm |  Permalink
    Very nice description.
    I have a comment on the last image (where the zoom-level is 18):
    The lattitude-value should increase from the bottom to the top. That is
    lat@bottom + Delta(lat) = lat@top
    In the image you have
    61.191934+0.004957=61.187178
    In the example for zoomlevel 20 everything is OK.
    Thanks again for the very good explanation!

  2. hustmobile March 30, 2010 at 3:04 am |  Permalink
    This article is great and very clear. But it seems have a little bug in calculation.
    “The pixel area continues to double at each zoom level, and when zoom level 20 is reached, the map is 536,870,912 pixels by 536,870,912 pixels. It has so many tiles we won’t bother to count them:”
    From the quotes, in zoom level 20 , the map is 536,870,912 pixels by 536,870,912 pixels. But according to the earlier article, in zoom level 20 , the map is 256×2^20 pixels by 256×2^20 pixels, so it should be 268435456 pixels by 268435456 pixels.
    Correct me if I am wrong.

  3. mothersh1p March 30, 2010 at 1:37 pm |  Permalink
    @hustmobile:
    Because if want you to convert latitude and longitude to pixel values (at zoom level 20)
    >#define MERCATOR_OFFSET 268435456 /* (total pixels at zoom level 20) / 2 */
    >#define MERCATOR_RADIUS 85445659.44705395 /* MERCATOR_OFFSET / pi */
    >
    >x = round(MERCATOR_OFFSET + MERCATOR_RADIUS * longitude * M_PI / 180.0);
    >y = round(MERCATOR_OFFSET – MERCATOR_RADIUS * logf((1 + sinf(latitude * M_PI / >180.0)) / (1 – sinf(latitude * M_PI / 180.0))) / 2.0);
    it divide the map in 4 tiles. North,East,South,West directions. That’s the definition of a quadtree. So 268435456 x 268435456 is the midpoint of 536870912 x 536,870,912.

  4. dental hygienist April 9, 2010 at 7:25 am |  Permalink
    Terrific work! This is the type of information that should be shared around the web. Shame on the search engines for not positioning this post higher!

  5. school grants April 20, 2010 at 12:56 am |  Permalink
    nice post. thanks.

  6. dental hygienist April 26, 2010 at 6:41 am |  Permalink
    Keep posting stuff like this i really like it
  7. lepah April 30, 2010 at 11:32 pm |  Permalink excellent plain english article that explains projections. I wish you had written this when I started working on iphone maps 

  8. MarkSpizer May 3, 2010 at 1:58 pm |  Permalink
    great post as usual!
  9. Tarto May 9, 2010 at 9:59 am |  Permalink
    Great article, thanks for sharing !
  10. Tarto May 9, 2010 at 10:06 am |  Permalink
    Just one comment : in scaling zoom level, it is written that zooming out doubles the area, isnt it that it double the distances, i.e quadruple the area ?

  11. Melva Mayo May 28, 2010 at 6:35 pm |  Permalink
    Very interesting writing! Honest..

  12. Larry Metzger May 30, 2010 at 8:47 am |  Permalink
    If I had a dollar for each time I came here.. Great writing!

  13. Marcus Lange May 31, 2010 at 7:20 pm |  Permalink
    Haha am I honestly the first comment to your incredible post?!?

  14. Kristen Tate June 1, 2010 at 6:12 am |  Permalink
    If only more than 84 people could read this.
  15. Sando September 16, 2010 at 10:24 am |  Permalink
    Excellent! Thank you very much!

  16. Jonathan Addleman September 16, 2010 at 7:18 pm |  Permalink
    I’m a little confused why you’re saying that zoom level 20 has a map that’s 536,870,912^2 pixels – shouldn’t it be half that? 256 x 2^20 = 268435456, doesn’t it?
    I think this is what hustmobile was asking above.
    I can see that you’d have to divide this number in half again to get the ‘mercator offset’ for the code, but it still seems the number is off.
  17. iPortable October 12, 2010 at 12:16 pm |  Permalink hey want to comment that the zoom level doesn’t start at 0 but from 1 to 21. (you should update the source code). Because if you compare the iPhone map for example on zoom 16 with the map on the google webpage, you will see 
    simply change “NSInteger zoomExponent = 20 – zoomLevel;” to “…21 – …”
  18. Chris Paynter October 20, 2011 at 1:42 am |  Permalink
    This article is great Troy, thanks a lot!
  19. Chris Paynter October 20, 2011 at 2:16 am |  Permalink

1.     
 I have one question. Why do you use:
 // clamp large numbers to 28
 zoomLevel = MIN(zoomLevel, 28);
 when the maximum zoom levels are 20?

  1. Jeff Sawatzky January 23, 2013 at 3:43 am |  Permalink

1.  Is there a reason you don’t use the built in methods to convert from latitude/longitude to pixel space? Namely the methods MKMapPointForCoordinate and MKCoordinateForMapPoint? Using these methods I think I modified your code to do the same thing. Basically I removed your conversiont code and then modified the helper method like so:
 - (MKCoordinateSpan)coordinateSpanWithMapView:(MKMapView *)mapView
 centerCoordinate:(CLLocationCoordinate2D)centerCoordinate
 andZoomLevel:(NSUInteger)zoomLevel
 {
 // convert center coordiate to pixel space
 MKMapPoint center = MKMapPointForCoordinate(centerCoordinate);
 // determine the scale value from the zoom level
 NSInteger zoomExponent = 20 – zoomLevel;
 double zoomScale = pow(2, zoomExponent);
 // scale the map’s size in pixel space
 CGSize mapSizeInPixels = mapView.bounds.size;
 CGFloat scaledMapWidth = mapSizeInPixels.width * zoomScale;
 CGFloat scaledMapHeight = mapSizeInPixels.height * zoomScale;
 // figure out the position of the top-left pixel
 MKMapPoint topLeftPoint = MKMapPointMake(center.x – (scaledMapWidth / 2), center.y – (scaledMapHeight / 2));
 MKMapPoint minPoint = MKMapPointMake(topLeftPoint.x, topLeftPoint.y);
 CLLocationCoordinate2D minCoord = MKCoordinateForMapPoint(minPoint);
 MKMapPoint maxPoint = MKMapPointMake(topLeftPoint.x + scaledMapWidth, topLeftPoint.y + scaledMapHeight);
 CLLocationCoordinate2D maxCoord = MKCoordinateForMapPoint(maxPoint);
 // find delta between left and right longitudes
 CLLocationDegrees longitudeDelta = maxCoord.longitude – minCoord.longitude;
 // find delta between top and bottom latitudes
 CLLocationDegrees latitudeDelta = -1 * (maxCoord.latitude – minCoord.latitude);
 // create and return the lat/lng span
 MKCoordinateSpan span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta);
 return span;
 }

  1. Jeff Sawatzky January 23, 2013 at 4:21 am |  Permalink
    Hmm, I’ve noticed a slight issue with my previous code, when I set the zoom to 0 or 1 I get an error, but all other zoom levels seem to work fine, and produce similar results to what your code does.
  2. Jeff Sawatzky January 23, 2013 at 4:25 am |  Permalink
    Reverting back to your code, I noticed that I also get an error with zoom level 0, but not zoom level 1.
  3. Ricky February 6, 2013 at 3:35 pm |  Permalink
    This is just so awesome. I believe my first comment on any blog. Thank you so much!

  4. informal wedding dresses tea length February 23, 2013 at 4:57 pm |  Permalink
    This is very interesting, You are an overly skilled blogger.
    I’ve joined your rss feed and sit up for in quest of extra of your great post. Also, I have shared your website in my social networks
  5. Whirlwind May 30, 2013 at 9:38 am |  Permalink Are you sure that the Zoom Level in the example is 18? I test:

1.  CLLocationCoordinate2D center = CLLocationCoordinate2DMake(61.189556, -149.826082);
 MKCoordinateSpan span = MKCoordinateSpanMake(0.004757, 0.006866);
 CGSize mapViewSize = CGSizeMake(1280, 1840);
 I got the zoom level is 17.
 And I also compute the latitudes and longitudes over zoom level 17, I got the region:
 (MKCoordinateRegion) region = {
 center = {
 latitude = 61.189556
 longitude = -149.826082
 }
 span = {
 latitudeDelta = 0.00475692749023438
 longitudeDelta = 0.006866455078125
 }
 }
 It seem right when zoom level is 17, not 18.
 please tell me if I am wrong.


  1. Nick Floussov July 4, 2013 at 11:17 pm |  Permalink
    OK,
    I don’t care about zoom level explanations later on but clearly
    sequence of pics with zoom of 0, 1, 2, .., 20
    should have relative width/heights as 256, 512, 1024, … 268435456.

  2. Nick Floussov July 9, 2013 at 6:24 pm |  Permalink
    Just a few more comments.
    1. take a look at this implementation: https://github.com/klokantech/Apple-WWDC10-TileMap and read comments in the source.
    2. check MKMapWorld structure to see that in fact the map size is 268435456 x 268435456.
    3. Apple does use top-to bottom approach which is described here.
    I mean, it goes not from zoom [0] UP to the current one but from zoom [20] DOWN to the current one; hence, all these ‘weird’ calculations posted here are exactly what Apple is doing.
    Just compare results from:
    MKMapPoint center = MKMapPointForCoordinate(centerCoordinate);
    and
    the ones from formulas in “Latitudes and Longitudes to Pixels”
    as it was mentioned by Jeff Sawatzky before.
    4. For Apple zoom [0] means in fact zoom [1], i.e. 4 tiles. That is why the whole thing crashes at zoom [0].

  3. www.mapadelamemoria.com October 10, 2014 at 3:54 pm |  Permalink
    Asking questions are genuinely agreeable thing if you are not understanding something completely whatsoever this story gives pleasant understanding even.

  4. www.skz.co.il December 13, 2015 at 11:06 pm |  Permalink
    Amazing movie,actually a fine quality, this YouTube movie touched me a lot amid terms of features.


举报

相关推荐

0 条评论