<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Backspace Prologue &#187; mapkit</title>
	<atom:link href="http://troybrant.net/blog/tag/mapkit/feed/" rel="self" type="application/rss+xml" />
	<link>http://troybrant.net/blog</link>
	<description>Mistakes and learnings of an iPhone developer</description>
	<lastBuildDate>Fri, 08 Jul 2011 22:15:40 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>MKMapView and Zoom Levels: A Visual Guide</title>
		<link>http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/</link>
		<comments>http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/#comments</comments>
		<pubDate>Sun, 31 Jan 2010 00:11:35 +0000</pubDate>
		<dc:creator>Troy</dc:creator>
				<category><![CDATA[iPhone Development]]></category>
		<category><![CDATA[mapkit]]></category>
		<category><![CDATA[maps]]></category>

		<guid isPermaLink="false">http://troybrant.net/blog/?p=8</guid>
		<description><![CDATA[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 class.
Round to Flat
This is planet Earth:

As you may know, it is round.
To create a map of the Earth, the [...]]]></description>
			<content:encoded><![CDATA[<p>So, how exactly does the code provided in the <a href="http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/">previous post</a> work? What follows is a visual explanation of Google Maps, zoom levels, and how you go about adding support for zoom levels to the <code>MKMapView</code> class.</p>
<h2>Round to Flat</h2>
<p>This is planet Earth:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/1-globe.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/1-globe-300x300.png" alt="" title="The Earth" width="300" height="300" class="aligncenter size-medium wp-image-17" /></a></p>
<p>As you may know, it is round.</p>
<p>To create a map of the Earth, the curved surface must be projected onto a flat surface. There are many <a href="http://en.wikipedia.org/wiki/Map_projection">map projections</a> 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.</p>
<p>Some projections preserve area, such as the <a href="http://en.wikipedia.org/wiki/Mollweide_projection">Mollweide projection</a>:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/3-mollweide-projection.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/3-mollweide-projection-300x150.png" alt="" title="Mollweide Projection" width="300" height="150" class="aligncenter size-medium wp-image-19" /></a></p>
<p><a href="http://en.wikipedia.org/wiki/Equirectangular_projection">Equirectangluar projections</a> preserve distance between the <a href="http://en.wikipedia.org/wiki/Meridian_(geography)">meridians</a>:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/4-equirectangular-projection.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/4-equirectangular-projection-300x150.png" alt="" title="Equirectangular Projection" width="300" height="150" class="aligncenter size-medium wp-image-20" /></a></p>
<p>The  <a href="http://en.wikipedia.org/wiki/Mercator_projection">Mercator projection</a> stretches out the poles in order to preserve locally measured angles:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/2-mercator-projection.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/2-mercator-projection-300x232.png" alt="" title="Mercator Projection" width="300" height="232" class="aligncenter size-medium wp-image-21" /></a></p>
<p>Google uses the Mercator projection to render <a href="http://maps.google.com/?ie=UTF8&#038;hq=&#038;h&#038;ll=1.406109,-35.859375&#038;spn=175.646868,360&#038;z=1">Google Maps</a>:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/5-mercator-equals-google.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/5-mercator-equals-google-1024x442.png" alt="" title="The Mercator projection is used by Google Maps" width="600" height="258" class="aligncenter size-large wp-image-25" /></a></p>
<h2>Mercator Math</h2>
<p>The Mercator projection converts latitude (φ) and longitude (λ) coordinates to pixel values. It uses math:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/6-mercator-equations.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/6-mercator-equations.png" alt="" title="Equations for converting latitude and longitude coordinates to pixels" width="213" height="208" class="aligncenter size-full wp-image-26" /></a></p>
<p>You don&#8217;t have to understand the math<a href="http://theoatmeal.com/comics/semicolon">;</a> just know that it converts latitudes and longitudes to pixels.</p>
<p>But, where are these pixels? Well, it depends on your zoom level.</p>
<h2>Zoom Levels</h2>
<p>At zoom level 0, Google displays the world in a single 256 pixel by 256 pixel tile:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/7-zoom-level-0.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/7-zoom-level-0.png" alt="" title="Zoom level 0" width="600" height="480" class="aligncenter size-full wp-image-28" /></a></p>
<p>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:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/8-zoom-level-1.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/8-zoom-level-1.png" alt="" title="Zoom level 1" width="600" height="480" class="aligncenter size-full wp-image-29" /></a></p>
<p>At zoom level 2, Google doubles the area again. The map grows to 1024 pixels by 1024 pixels and uses sixteen tiles:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/9-zoom-level-2.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/9-zoom-level-2.png" alt="" title="Zoom level 2" width="600" height="480" class="aligncenter size-full wp-image-30" /></a></p>
<p>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&#8217;t bother to count them:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/10-zoom-level-20.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/10-zoom-level-20.png" alt="" title="Zoom level 20" width="600" height="480" class="aligncenter size-full wp-image-32" /></a></p>
<h2>Latitudes and Longitudes to Pixels</h2>
<p>As part of the <a href="http://www.appelsiini.net/2008/10/simple-static-maps-with-php">PHP Static Maps project</a>, <a href="http://www.appelsiini.net/">Mike Tuupola</a> wrote <a href="http://github.com/tuupola/php_google_maps/blob/master/Google/Maps/Mercator.php">some code</a> that converts latitudes and longitudes to pixels at zoom level 20. The code is easily ported to Objective-C:</p>
<div class="code">
<span class="comment">// Convert latitude and longitude to pixel values at zoom level 20</span></p>
<p><span class="preprocessor">#define MERCATOR_OFFSET <span class="number">268435456</span></span> <span class="comment">/* (total pixels at zoom level 20) / 2 */</span><br />
<span class="preprocessor">#define MERCATOR_RADIUS <span class="number">85445659.44705395</span></span> <span class="comment">/* MERCATOR_OFFSET / pi */</span></p>
<p>x = <span class="built_in_type">round</span>(<span class="constant">MERCATOR_OFFSET</span> + <span class="constant">MERCATOR_RADIUS</span> * <span class="var">longitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>);<br />
y = <span class="built_in_type">round</span>(<span class="constant">MERCATOR_OFFSET</span> &#45; <span class="constant">MERCATOR_RADIUS</span> * <span class="built_in_type">logf</span>((<span class="number">1</span> + <span class="built_in_type">sinf</span>(<span class="var">latitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>)) / (<span class="number">1</span> &#45; <span class="built_in_type">sinf</span>(<span class="var">latitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>))) / <span class="number">2</span>.<span class="number">0</span>);
</div>
<p>To be honest, I haven&#8217;t taken the time to wrap my head around how this code works. But, knowing that it <em>does</em> work, we can now take any latitude and longitude and figure out its pixel coordinates at zoom level 20. For instance, here are the pixel coordinates of several cities around the world:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/11-pixel-location-of-cities.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/11-pixel-location-of-cities.png" alt="" title="Pixel location of cities at zoom level 20" width="600" height="480" class="aligncenter size-full wp-image-33" /></a></p>
<h2>Add an iPhone</h2>
<p>Say we place an iPhone on top of Anchorage, Alaska at zoom level 20:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/12-iphone-on-anchorage-zoom-20.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/12-iphone-on-anchorage-zoom-20.png" alt="" title="Anchorage, Alaska at zoom level 20" width="549" height="480" class="aligncenter size-full wp-image-60" /></a></p>
<p>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:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/13-top-left-zoom-201.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/13-top-left-zoom-201.png" alt="" title="Relative position of the top-left corner" width="296" height="480" class="aligncenter size-full wp-image-61" /></a></p>
<p>We can find the relative position of the top-right and bottom-left pixel coordinates as well:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/14-ritght-and-bottom-zoom-201.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/14-ritght-and-bottom-zoom-201.png" alt="" title="Relative position of the top-right and bottom-left corners" width="296" height="480" class="aligncenter size-full wp-image-62" /></a></p>
<p>The PHP Static Maps code also provides <a href="http://github.com/tuupola/php_google_maps/blob/master/Google/Maps/Mercator.php">code</a> to go from pixels at zoom level 20 to latitudes and longitudes:</p>
<div class="code">
<span class="comment">// Convert pixel values at zoom level 20 to latitude and longitude</span></p>
<p>latitude = (<span class="constant">M_PI</span> / <span class="number">2</span>.<span class="number">0</span> &#45; <span class="number">2</span>.<span class="number">0</span> * <span class="built_in_type">atan</span>(<span class="built_in_type">exp</span>((<span class="built_in_type">round</span>(<span class="var">pixelY</span>) &#45; <span class="constant">MERCATOR_OFFSET</span>) / <span class="constant">MERCATOR_RADIUS</span>))) * <span class="number">180</span>.<span class="number">0</span> / <span class="constant">M_PI</span>;<br />
longitude = ((<span class="built_in_type">round</span>(<span class="var">pixelX</span>) &#45; <span class="constant">MERCATOR_OFFSET</span>) / <span class="constant">MERCATOR_RADIUS</span>) * <span class="number">180</span>.<span class="number">0</span> / <span class="constant">M_PI</span>;
</div>
<p>We can use this code to convert the corners from pixel coordinates to latitudes and longitudes:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/15-lat-lng-corners-zoom-201.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/15-lat-lng-corners-zoom-201.png" alt="" title="Corner pixels are converted to latitude and longitudes" width="511" height="480" class="aligncenter size-full wp-image-63" /></a></p>
<p>As shown above, using the corner coordinates, we can compute the latitudinal and longitudinal distances. These distances are exactly what we need to construct an <code>MKCoordinateSpan</code>. That span, in turn, is used to initialize the region property of an <code>MKMapView</code>:</p>
<div class="code">
<span class="comment">// Create an MKCoordinateSpan to initialize the map&#8217;s region</span></p>
<p><span class="built_in_type">MKCoordinateSpan</span> <span class="var">span</span> = <span class="built_in_type">MKCoordinateSpanMake</span>(<span class="var">latitudeDelta</span>, <span class="var">longitudeDelta</span>);<br />
<span class="built_in_type">MKCoordinateRegion</span> <span class="var">region</span> = <span class="built_in_type">MKCoordinateRegionMake</span>(<span class="var">centerCoordinate</span>, <span class="var">span</span>);<br />
[<span class="ivar">mapView</span> <span class="method_call">setRegion</span>:<span class="var">region</span> <span class="method_call">animated</span>:<span class="keyword">NO</span>];
</div>
<p>And you&#8217;re done!&#8230;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?</p>
<h2>Scaling using Zoom Levels</h2>
<p>Relative to zoom level 20, zooming out one level doubles the area visible on the map.</p>
<p>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:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/16-anchorage-zoom-19.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/16-anchorage-zoom-19.png" alt="" title="On the left, Anchorage at zoom level 19. On the right, the number of iPhones at zoom level 20 it would take to display the same area." width="560" height="401" class="aligncenter size-full wp-image-52" /></a></p>
<p>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:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/17-anchorage-zoom-18.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/17-anchorage-zoom-18.png" alt="" title="On the left, Anchorage at zoom level 18. On the right, the number of iPhones at zoom level 20 it would take to display the same area." width="560" height="405" class="aligncenter size-full wp-image-51" /></a></p>
<p>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:</p>
<div class="code">
<span class="comment">// Compute a scaling factor that will take us from any zoom level to zoom level 20</span></p>
<p><span class="built_in_type">NSInteger</span> <span class="var">zoomExponent</span> = <span class="number">20</span> &#45; <span class="var">zoomLevel</span>;<br />
<span class="primitive">double</span> <span class="var">zoomScale</span> = <span class="var">pow</span>(<span class="number">2</span>, <span class="var">zoomExponent</span>);</p>
<p><span class="primitive">double</span> <span class="var">scaledMapWidth</span> = <span class="var">mapSizeInPixels</span>.<span class="property_call">width</span> * <span class="var">zoomScale</span>;<br />
<span class="primitive">double</span> <span class="var">scaledMapHeight</span> = <span class="var">mapSizeInPixels</span>.<span class="property_call">height</span> * <span class="var">zoomScale</span>;
</div>
<p>For instance, here is Anchorage at zoom levels 20, 19, and 18. The map&#8217;s width and height in pixels are unaltered:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/18-anchorage-zoom-20-19-18.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/18-anchorage-zoom-20-19-18.png" alt="" title="Anchorage at zoom levels 20, 19, and 18." width="560" height="349" class="aligncenter size-full wp-image-53" /></a></p>
<p>After computing the zoom scale factor, we can apply it to each map to determine its dimensions at zoom level 20:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/19-anchorage-zoom-20-20-20.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/19-anchorage-zoom-20-20-20.png" alt="" title="The result of converting zoom levels 20, 19, and 18 to zoom level 20." width="560" height="358" class="aligncenter size-full wp-image-54" /></a></p>
<p>After we compute these new dimensions, we plug them into the algorithm for finding the coordinates of the map corners.</p>
<h2>An Example: Zoom Level 18</h2>
<p>For instance, say we take the map at zoom level 18:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/20-zoom-18-to-20-matrix.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/20-zoom-18-to-20-matrix.png" alt="" title="Zoom level 18 scaled to zoom level 20" width="269" height="325" class="aligncenter size-full wp-image-55" /></a></p>
<p>Let&#8217;s drop the matrix of phones but keep the scaled width and height:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/21-zoom-18-to-20-no-matrix.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/21-zoom-18-to-20-no-matrix.png" alt="" title="Zoom level 18 scaled to zoom level 20" width="309" height="480" class="aligncenter size-full wp-image-56" /></a></p>
<p>We find the top-left corner just like we did before, except now we use the scaled width and height:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/22-zoom-18-top-left.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/22-zoom-18-top-left.png" alt="" title="Relative position of top-left corner using scaled dimensions" width="310" height="480" class="aligncenter size-full wp-image-57" /></a></p>
<p>Similarly, we use the scaled width and height for finding the top-right and bottom-left corners as well:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/23-zoom-18-other-corners.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/23-zoom-18-other-corners.png" alt="" title="Relative position of top-right and bottom-left corners using scaled dimensions" width="309" height="480" class="aligncenter size-full wp-image-58" /></a></p>
<p>Using the pixel and latitude and pixel and longitude helper methods, we can compute the coordinates of the corners and the distance between them:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/24-zoom-18-lat-lng-corners.png" rel="lightbox[8]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/24-zoom-18-lat-lng-corners.png" alt="" title="Pixels at zoom level 20 are converted to latitudes and longitudes" width="511" height="480" class="aligncenter size-full wp-image-59" /></a></p>
<p>These delta values are used to initialize the map&#8217;s region property, and the map zooms to the level you specify.</p>
<h2>That&#8217;s a Wrap</h2>
<p>Be sure to check out the <a href="">previous post</a> for the full code that adds support for zoom levels to <code>MKMapView</code>.</p>
<p>If you are interested in learning more from someone much smarter than I am, check out these posts from <a href="http://cfis.savagexi.com/pages/about">Charlie Savage</a>, a programmer and cartographer extraordinaire:</p>
<ul>
<li><a href="http://cfis.savagexi.com/2006/05/03/google-maps-deconstructed">Google Maps Deconstructed</a></li>
<li><a href="http://cfis.savagexi.com/2006/05/05/google-maps-revisited">Google Maps Revisted</a></li>
<li><a href="http://cfis.savagexi.com/2006/06/30/mouse-coordinates-to-lat-long">Mouse Coordinates to Lat/Long</a></li>
</ul>
<p>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.</p>
]]></content:encoded>
			<wfw:commentRss>http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/feed/</wfw:commentRss>
		<slash:comments>19</slash:comments>
		</item>
		<item>
		<title>Set the Zoom Level of an MKMapView</title>
		<link>http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/</link>
		<comments>http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/#comments</comments>
		<pubDate>Fri, 22 Jan 2010 22:59:25 +0000</pubDate>
		<dc:creator>Troy</dc:creator>
				<category><![CDATA[iPhone Development]]></category>
		<category><![CDATA[mapkit]]></category>
		<category><![CDATA[maps]]></category>

		<guid isPermaLink="false">http://troybrant.net/blog/?p=6</guid>
		<description><![CDATA[If you have ever built a web application using the Google Maps API, you are likely intimately familiar with this line of code:

map.setCenter(new google.maps.LatLng(37.4419, -122.1419), 13);

The setCenter JavaScript method takes in the center coordinate and a zoom level. The zoom level, as you might expect, determines how far the map should zoom in. The zoom [...]]]></description>
			<content:encoded><![CDATA[<p>If you have ever built a web application using the Google Maps API, you are likely intimately familiar with this line of code:</p>
<div class="code">
<span class="ivar">map</span>.setCenter(<span class="keyword">new</span> google.maps.<span class="built_in_type">LatLng</span>(<span class="number">37.4419</span>, -<span class="number">122</span>.<span class="number">1419</span>), <span class="number">13</span>);
</div>
<p>The <code>setCenter</code> JavaScript method takes in the center coordinate and a zoom level. The zoom level, as you might expect, determines how far the map should zoom in. The zoom level ranges from 0 (all the way zoomed out) to some upper value (all the way zoomed in). The max zoom level for a particular area depends on the location (for instance, you can&#8217;t zoom in too far on North Korea) and the map type (default, satellite, hybrid, terrain, etc). Typically, the max zoom level for an area is somewhere between 15 &#8211; 21.</p>
<p>Unfortunately, <code>MapKit</code> on the iPhone does not include a way to set the zoom level. Instead, the zoom level is set <em>implicitly</em> by defining the <code>MKCoordinateRegion</code> of the map&#8217;s viewport. When initializing the region, you specify the amount of distance the map displays in the horizontal and vertical directions. The zoom level is set implicitly based on these distance values.</p>
<p>Instead of dealing with this region business, I wrote a category that adds support for setting the zoom level of an <code>MKMapView</code> explicitly. In this post, I&#8217;ll give you code you can drop into your own projects and start using immediately. <a href="http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/">My next post</a> will detail exactly how it works.</p>
<h2>The Code</h2>
<p>Instead of force-feeding you everything I learned while working on this mini-project, I&#8217;ll give you the goods up front. The code below defines a category on <code>MKMapView</code> that gives you the ability to set the zoom level for your map:</p>
<div class="code">
<span class="comment">//  MKMapView+ZoomLevel.h</span> </p>
<p><span class="preprocessor">#import <span class="string">&lt;MapKit/MapKit.h&gt;</span></span> </p>
<p><span class="directive">@interface</span> MKMapView (ZoomLevel) </p>
<p>- (<span class="primitive">void</span>)setCenterCoordinate:(<span class="built_in_type">CLLocationCoordinate2D</span>)centerCoordinate<br />
&nbsp;&nbsp;&nbsp;&nbsp;zoomLevel:(<span class="built_in_type">NSUInteger</span>)zoomLevel<br />
&nbsp;&nbsp;&nbsp;&nbsp;animated:(<span class="primitive">BOOL</span>)animated;</p>
<p><span class="directive">@end</span>
</div>
<div class="code">
<span class="comment">//  MKMapView+ZoomLevel.m</span> </p>
<p><span class="preprocessor">#import <span class="string">&quot;MKMapView+ZoomLevel.h&quot;</span></span> </p>
<p><span class="preprocessor">#define MERCATOR_OFFSET <span class="number">268435456<br />
</span></span> <span class="preprocessor">#define MERCATOR_RADIUS <span class="number">85445659.44705395</p>
<p></span></span> <span class="directive">@implementation</span> MKMapView (ZoomLevel) </p>
<p><span class="preprocessor">#pragma mark -</span><br />
<span class="preprocessor">#pragma mark Map conversion methods</span> </p>
<p>- (<span class="primitive">double</span>)longitudeToPixelSpaceX:(<span class="primitive">double</span>)longitude<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="built_in_type">round</span>(<span class="constant">MERCATOR_OFFSET</span> + <span class="constant">MERCATOR_RADIUS</span> * <span class="var">longitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>);<br />
} </p>
<p>- (<span class="primitive">double</span>)latitudeToPixelSpaceY:(<span class="primitive">double</span>)latitude<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="built_in_type">round</span>(<span class="constant">MERCATOR_OFFSET</span> &#45; <span class="constant">MERCATOR_RADIUS</span> * <span class="built_in_type">logf</span>((<span class="number">1</span> + <span class="built_in_type">sinf</span>(<span class="var">latitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>)) / (<span class="number">1</span> &#45; <span class="built_in_type">sinf</span>(<span class="var">latitude</span> * <span class="constant">M_PI</span> / <span class="number">180</span>.<span class="number">0</span>))) / <span class="number">2</span>.<span class="number">0</span>);<br />
} </p>
<p>- (<span class="primitive">double</span>)pixelSpaceXToLongitude:(<span class="primitive">double</span>)pixelX<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> ((<span class="built_in_type">round</span>(<span class="var">pixelX</span>) &#45; <span class="constant">MERCATOR_OFFSET</span>) / <span class="constant">MERCATOR_RADIUS</span>) * <span class="number">180</span>.<span class="number">0</span> / <span class="constant">M_PI</span>;<br />
} </p>
<p>- (<span class="primitive">double</span>)pixelSpaceYToLatitude:(<span class="primitive">double</span>)pixelY<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> (<span class="constant">M_PI</span> / <span class="number">2</span>.<span class="number">0</span> &#45; <span class="number">2</span>.<span class="number">0</span> * <span class="built_in_type">atan</span>(<span class="built_in_type">exp</span>((<span class="built_in_type">round</span>(<span class="var">pixelY</span>) &#45; <span class="constant">MERCATOR_OFFSET</span>) / <span class="constant">MERCATOR_RADIUS</span>))) * <span class="number">180</span>.<span class="number">0</span> / <span class="constant">M_PI</span>;<br />
} </p>
<p><span class="preprocessor">#pragma mark -</span><br />
<span class="preprocessor">#pragma mark Helper methods</span> </p>
<p>- (<span class="built_in_type">MKCoordinateSpan</span>)coordinateSpanWithMapView:(<span class="built_in_type">MKMapView</span> *)mapView<br />
&nbsp;&nbsp;&nbsp;&nbsp;centerCoordinate:(<span class="built_in_type">CLLocationCoordinate2D</span>)centerCoordinate<br />
&nbsp;&nbsp;&nbsp;&nbsp;andZoomLevel:(<span class="built_in_type">NSUInteger</span>)zoomLevel<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// convert center coordiate to pixel space</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">centerPixelX</span> = [<span class="keyword">self</span> <span class="method_call">longitudeToPixelSpaceX</span>:<span class="var">centerCoordinate</span>.<span class="property_call">longitude</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">centerPixelY</span> = [<span class="keyword">self</span> <span class="method_call">latitudeToPixelSpaceY</span>:<span class="var">centerCoordinate</span>.<span class="property_call">latitude</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// determine the scale value from the zoom level</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">NSInteger</span> <span class="var">zoomExponent</span> = <span class="number">20</span> &#45; <span class="var">zoomLevel</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">zoomScale</span> = <span class="var">pow</span>(<span class="number">2</span>, <span class="var">zoomExponent</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// scale the map&#8217;s size in pixel space</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CGSize</span> <span class="var">mapSizeInPixels</span> = <span class="var">mapView</span>.<span class="property_call">bounds</span>.<span class="property_call">size</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">scaledMapWidth</span> = <span class="var">mapSizeInPixels</span>.<span class="property_call">width</span> * <span class="var">zoomScale</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">scaledMapHeight</span> = <span class="var">mapSizeInPixels</span>.<span class="property_call">height</span> * <span class="var">zoomScale</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// figure out the position of the top-left pixel</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">topLeftPixelX</span> = <span class="var">centerPixelX</span> &#45; (<span class="var">scaledMapWidth</span> / <span class="number">2</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="primitive">double</span> <span class="var">topLeftPixelY</span> = <span class="var">centerPixelY</span> &#45; (<span class="var">scaledMapHeight</span> / <span class="number">2</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// find delta between left and right longitudes</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">minLng</span> = [<span class="keyword">self</span> <span class="method_call">pixelSpaceXToLongitude</span>:<span class="var">topLeftPixelX</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">maxLng</span> = [<span class="keyword">self</span> <span class="method_call">pixelSpaceXToLongitude</span>:<span class="var">topLeftPixelX</span> + <span class="var">scaledMapWidth</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">longitudeDelta</span> = <span class="var">maxLng</span> &#45; <span class="var">minLng</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// find delta between top and bottom latitudes</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">minLat</span> = [<span class="keyword">self</span> <span class="method_call">pixelSpaceYToLatitude</span>:<span class="var">topLeftPixelY</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">maxLat</span> = [<span class="keyword">self</span> <span class="method_call">pixelSpaceYToLatitude</span>:<span class="var">topLeftPixelY</span> + <span class="var">scaledMapHeight</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationDegrees</span> <span class="var">latitudeDelta</span> = -<span class="number">1</span> * (<span class="var">maxLat</span> &#45; <span class="var">minLat</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// create and return the lat/lng span</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">MKCoordinateSpan</span> <span class="var">span</span> = <span class="built_in_type">MKCoordinateSpanMake</span>(<span class="var">latitudeDelta</span>, <span class="var">longitudeDelta</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">return</span> <span class="var">span</span>;<br />
} </p>
<p><span class="preprocessor">#pragma mark -</span><br />
<span class="preprocessor">#pragma mark Public methods</span> </p>
<p>- (<span class="primitive">void</span>)setCenterCoordinate:(<span class="built_in_type">CLLocationCoordinate2D</span>)centerCoordinate<br />
&nbsp;&nbsp;&nbsp;&nbsp;zoomLevel:(<span class="built_in_type">NSUInteger</span>)zoomLevel<br />
&nbsp;&nbsp;&nbsp;&nbsp;animated:(<span class="primitive">BOOL</span>)animated<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// clamp large numbers to 28</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="var">zoomLevel</span> = <span class="constant">MIN</span>(<span class="var">zoomLevel</span>, <span class="number">28</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// use the zoom level to compute the region</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">MKCoordinateSpan</span> <span class="var">span</span> = [<span class="keyword">self</span> <span class="method_call">coordinateSpanWithMapView</span>:<span class="keyword">self</span> <span class="method_call">centerCoordinate</span>:<span class="var">centerCoordinate</span> <span class="method_call">andZoomLevel</span>:<span class="var">zoomLevel</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">MKCoordinateRegion</span> <span class="var">region</span> = <span class="built_in_type">MKCoordinateRegionMake</span>(<span class="var">centerCoordinate</span>, <span class="var">span</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// set the region like normal</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">self</span> <span class="method_call">setRegion</span>:<span class="var">region</span> <span class="method_call">animated</span>:<span class="var">animated</span>];<br />
} </p>
<p><span class="directive">@end</span> </p>
</div>
<p>If you&#8217;re wondering why this works, check out <a href="http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/">my next post</a> where I describe in gruesome detail the math behind the code.</p>
<p>On the other hand, if you don&#8217;t really care <em>how</em> it works, just that it <em>does</em> work, copy and paste away, my friend.</p>
<h2>Test the Code</h2>
<p>To test the category, assuming you have a view controller with an <code>MKMapView</code> instance, you can use the following code:</p>
<div class="code">
<span class="comment">// ZoomLevelTestViewController.m</span></p>
<p><span class="preprocessor">#import <span class="string">&quot;MKMapView+ZoomLevel.h&quot;</span></span> </p>
<p><span class="preprocessor">#define GEORGIA_TECH_LATITUDE <span class="number">33.777328</span></span><br />
<span class="preprocessor">#define GEORGIA_TECH_LONGITUDE <span class="number">-84.397348</span></span> </p>
<p><span class="preprocessor">#define ZOOM_LEVEL <span class="number">14</span></span></p>
<p>- (<span class="primitive">void</span>)viewDidAppear:(<span class="primitive">BOOL</span>)animated<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="keyword">super</span> <span class="method_call">viewDidAppear</span>:<span class="var">animated</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span class="built_in_type">CLLocationCoordinate2D</span> <span class="var">centerCoord</span> = { <span class="constant">GEORGIA_TECH_LATITUDE</span>, <span class="constant">GEORGIA_TECH_LONGITUDE</span> };<br />
&nbsp;&nbsp;&nbsp;&nbsp;[<span class="ivar">mapView</span> <span class="method_call">setCenterCoordinate</span>:<span class="var">centerCoord</span> <span class="method_call">zoomLevel</span>:<span class="constant">ZOOM_LEVEL</span> <span class="method_call">animated</span>:<span class="keyword">NO</span>];<br />
}
</div>
<p>And, viola! Your map should zoom in to where you can see the Georgia Tech campus.</p>
<h2>Results</h2>
<p>To verify that the zoom level is set correctly, I wrote a simple web-based Google Maps application to make sure the web and native zoom levels matched. Both apps were centered at {33.777328, -84.397348} (Georgia Tech). In the images below, the iPhone on the left is running the native app and the iPhone on the right is running the web app:</p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-2.png" rel="lightbox[6]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-2-300x255.png" alt="Zoom Level 2: Native app on the left. Web app on the right." title="Zoom Level 2: Native app on the left. Web app on the right." width="300" height="255" class="aligncenter size-medium wp-image-14" /></a></p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-10.png" rel="lightbox[6]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-10-300x255.png" alt="Zoom Level 10: Native app on the left. Web app on the right." title="Zoom Level 10: Native app on the left. Web app on the right." width="300" height="255" class="aligncenter size-medium wp-image-15" /></a></p>
<p><a href="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-18.png" rel="lightbox[6]"><img src="http://troybrant.net/blog/wp-content/uploads/2010/01/zoom-level-18-300x255.png" alt="Zoom Level 18: Native app on the left. Web app on the right." title="Zoom Level 18: Native app on the left. Web app on the right." width="300" height="255" class="aligncenter size-medium wp-image-16" /></a></p>
<p>As you can see, they match.</p>
<h2>That&#8217;s a Wrap</h2>
<p>By using the <code>MKMapView+ZoomLevel</code> category, you won&#8217;t have to bother setting the region at all. If you are like me and have no intuition for how to set the map&#8217;s region, then hopefully this code will give you a bit more control in setting your map&#8217;s zoom level.</p>
<p><a href="http://troybrant.net/blog/2010/01/mkmapview-and-zoom-levels-a-visual-guide/">Next time</a>, I&#8217;ll go over exactly why the code above works. But, for now, enjoy the freedom to set zoom levels!</p>
]]></content:encoded>
			<wfw:commentRss>http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/feed/</wfw:commentRss>
		<slash:comments>41</slash:comments>
		</item>
	</channel>
</rss>

