Overlay WMS on 3D maps on iOS

After more than 3 years it still amazes me how  good is  myVR  technology  for 3D  maps on  iOS.  Watch Czech Republic DMT with CUZK orthophoto as base map  and cadastral or geography WMS overlay:

Advertisements

WMS overlay with MapBox-gl-js 0.5.2

alt textQuick and dirty test of the WMS capabilities of the new MapBox-gl-js 0.5.2 API. First of all, yes ! it is possible to overlay (legacy) WMS over the vector WebGL rendered base map … however the way is not straightforward:

 

  • Needs some ‘hacks’ as current version of the API doesn’t have enough events to supply custom URL before it is loaded. But check latest version of mapbox, it might have better support for this.
  • Another issue is that WMS server has to provide HTTP header with Access-Control-Allow-Origin:* to avoid WebGL CORS failure when loading image (gl.texImage2D). Usually WMS servers don’t care about this, as for normal img tags CORS doesn’t apply. Here WebGL has access to raw image data so WMS provider has to explicitly agree with this.
  • Build process of mapbox-gl-js tend to be as many other large js projects complicated, slow, complex. And specifically on Windows platform it is more difficult to get mapbox-gl-js install and build running then on Mac.

Code is documented to guide you through the process, few highlights:


 // -- rutine originaly found in GlobalMercator.js, simplified
 // -- calculates spherical mercator coordinates from tile coordinates
 function tileBounds(tx, ty, zoom, tileSize) {
    function pixelsToMeters(px, py, zoom) {
     var res = (2 * Math.PI * 6378137 / 256) / Math.pow(2, zoom),
         originShift = 2 * Math.PI * 6378137 / 2,
         x = px * res - originShift,
         y = py * res - originShift;
     return [Math.abs(x), Math.abs(y)];
     };
   var min = pixelsToMeters(tx * tileSize, ty * tileSize, zoom),
         max = pixelsToMeters((tx + 1) * tileSize, (ty + 1) * tileSize, zoom);
return min.concat(max);
}

 
]

// -- save orig _loadTile function so we can call it later
 // -- there was no good pre-load event at mapbox API to get hooked and patch url
// -- we need to use undocumented _loadTile
 var origFunc = sourceObj._loadTile;
    // -- replace _loadTile with own implementation
 sourceObj._loadTile = function (id) {
    // -- we have to patch sourceObj.url, dirty !
    // -- we basically change url on the fly with correct BBOX coordinates
    // -- and leave rest on original _loadTile processing
     var origUrl =sourceObj.tiles[0]
                      .substring(0,sourceObj.tiles[0].indexOf('&BBOX'));
     var origUrl = origUrl +"&BBOX={mleft},{mbottom},{mright},{mtop}";
     sourceObj.tiles[0] = patchUrl(id, [origUrl]);
     // -- call original method
     return  origFunc.call(sourceObj, id);
 }

 

 

gist available here

WMS overlay on Windows 8.1 and Windows Phone 8.1

wp_ss_20140523_0001I agree with this blog “Learning how to use Maps in the .Net world is a never ending business. There are major API-differences between WPF, Silverlight, Windows 8 Store apps and Windows Phone and even between WP7 and WP8. Windows Phone 8.1 makes no differ and there are many breaking changes here”

my tests of previous WMS overlays are listed here:

Silverlight : https://blog.sumbera.com/2010/02/25/overlay-wms-on-silverlight-bing/
Windows Phone 7: https://blog.sumbera.com/2011/02/18/windows-phone-7-wms-test/
Windows Phone 8: https://blog.sumbera.com/2013/03/10/tiled-wms-overlay-in-windows-phone-8/

I have put together Visual studio 2013, Update2 Solution that does WMS overlay in Windows 8.1 and Windows Phone 8.1 called WMSonWin81 – here on github

Video of Windows Phone 8.1 below, used “Project My Screen App” to project the app on desktop and record.

WMSonWin81
WMSonWin81using universal app for Windows Store and Windows Phone Store apps Both apps do not share same namespace for map nor component. Windows Store is using Bing Map while Windows Phone is using map control as part of the WP8.1 framework located in Windows.UI.Xaml.Controls.Maps

there are main 2 projects:

WMSOnWin.Windows
Sample code of using WMS source on Windows Store Apps using Bing Maps:

MapTileLayer mapTileLayer = new MapTileLayer();
     mapTileLayer.GetTileUri += delegate(object sender, GetTileUriEventArgs e) {
     Rect mercBounds = GlobalMercator.TileBounds(new Tile(e.X, e.Y), e.LevelOfDetail);
     e.Uri = new Uri(string.Format(_wmsUrl, mercBounds.Left, 
           Math.Abs(mercBounds.Bottom), mercBounds.Right, Math.Abs(mercBounds.Top)));
 };

_bingMap.TileLayers.Add(mapTileLayer);
WMSOnWin.WindowsPhone

Sample code of using WMS on Windows Phone Store App using Windows.UI.Xaml.Controls.Maps; core class to look is HttpMapTileDataSource

    HttpMapTileDataSource dataSource = new HttpMapTileDataSource();
         dataSource.UriRequested += 
 new TypedEventHandler<HttpMapTileDataSource, MapTileUriRequestedEventArgs>(
 (source, args) => {
    Rect mercBounds = GlobalMercator.TileBounds(
                           new Tile(args.X, args.Y), args.ZoomLevel);
    args.Request.Uri = new Uri(string.Format(_wmsUrl, mercBounds.Left,
    Math.Abs(mercBounds.Bottom), mercBounds.Right, Math.Abs(mercBounds.Top))); ;
 });
  _map.TileSources.Add(new MapTileSource(dataSource));

Tested on Windows 8.1 64bit, and Lumia 810, Windows Phone 8.1

enjoy.

Credits: got help here to repair problems with USB connection to my Lumia 810 (had to uninstall USB driver for the phone in device manager)  and here to get WP8.1 on Lumia 810

WMS on MapKit with iOS7

Updated WMS over MapKit sample code for iOS7 , available on github  I have added cadastral maps of Czech Republic, used camera API to set the view and tested, check also WMS on Google  Maps SDK on iOS mentioned here

iOS7 introduced new class MKTileOverlay sample derives from this class WMSTileOverlay

Key method to custom tile loading (and cache control) is loadTileAtPath:result

- (void)loadTileAtPath:(MKTileOverlayPath)path result:(void (^)(NSData *tileData, NSError *error))  result

this method is called by MapKit (or better by MKTileOverlayRenderer ) when it needs to draw a tile . It asks for NSData (and error) from x,y,z tile coordinates. In this method you can  load NSData either from local cache or from NSURLConnection and pass resulting NSData (when ready)  back to MapKit, for example like this (reading from cache)

result ([NSData dataWithContentsOfFile:filePath], nil);

if you do not need to use cache and you do not provide loadTileAtPath method , you can use another hook (callback) that is provided by MKTileOverlay, URLForTilePath:path

- (NSURL *)URLForTilePath:(MKTileOverlayPath)path

this method enables to custom format URL required to load tile, thus you can use WMS HTTP-GET parameters, for example :

NSString * resolvedUrl = [NSString stringWithFormat:@"%@&BBOX=%f,%f,%f,%f",self.url,left,bottom,right,top];

if there is neither method in the derived class, then you probably do not need to derive at all from MKTileOverlay and directly use it with initWithUrlTemplate (not case for WMS, but for any other x,y,z  sources)

IMG_0021

Bad news is that  MapKit on iOS7 doesn’t support   tilt/pinch in Satellite/Hybrid mode in MapKit on iOS7

WMS with Google Maps on iOS

Screen Shot 2014-04-21 at 00.49.02Sample for using WMS sources in Google Maps SDK for iOS. available on github here: https://github.com/Sumbera/WMS_iOS_GoogleMapSDK
Provide your API key in the WMSController.h

  • Google Maps for iOS used : 1.7.2 (April 2014)
  • used XCode 5.1.1 (April 2014)
  • iPad Air, iOS 7.1 (should run in iOS6.0 too)

 

There are two ways of overlaying WMS in the Google Maps for iOS SDK:

“Method B”: use GMSTileURLConstructor

   // -- method B. WMS tile layer with GMSTileURLConstructor
      GMSTileURLConstructor urls = 
         ^(NSUInteger x, NSUInteger y, NSUInteger z) {
           BBox bbox = bboxFromXYZ(x,y,z);
           NSString *urlKN = 
             [NSString stringWithFormat:@"Your WMS url&BBOX=%f,%f,%f,%f",
                                           bbox.left,
                                           bbox.bottom,
                                           bbox.right,
                                           bbox.top];

          return [NSURL URLWithString:urlKN];
      };

“Method A”: use custom TileLayer derived from GMSTileLayer

  1. your derived class from GMSTileLayer (here WMSTileLayer.h) will receive tile request
     -(void)requestTileForX:(NSUInteger)x 
                                     y:(NSUInteger)y
                                  zoom:(NSUInteger)z 
                              receiver:(id<GMSTileReceiver>)receiver
    
  2. WMSTileLayer first checks for cached tile and if found calls :
      [self drawTileAtX:x y:y zoom:z Url:urlStr Receiver:receiver] ;
    
  3. if tile is not cached we download it, save it to the file system (using MD5 hash) and call to draw it
      [data  writeToFile: filePath  atomically:YES];
      [self drawTileAtX:x y: y zoom: z Url:urlStr Receiver:receiver] ;
    
  4. drawTileAtX is very simple:
      -(void) drawTileAtX: (NSUInteger) x 
                                   y:(NSUInteger) y
                                zoom:(NSUInteger)zoom
                                 Url:(NSString*) url
                            Receiver: (id<GMSTileReceiver>) receiver {
           UIImage             *image   = TileLoad(url,NO); 
           [receiver receiveTileWithX:x y:y zoom:zoom image:image]; 
      }
    

}

both ways are used in this sample.

Tiled WMS overlay in Windows Phone 8

WP8WMSUpdate May 2014: version for Windows Phone 8.1 is here: https://blog.sumbera.com/2014/05/23/wms-overlay-on-windows-8-1-and-windows-phone-8-1/ 

Looking on Windows Phone 8 Map SDK, I have tried to migrate a simple WMS overlay from Windows   Phone 7 (Bing map) – blogged here : https://blog.sumbera.com/2010/11/07/tiled-wms-overlay-in-windows-phone-7/

Changes :

– for migration you have to add before migration System.Core into the project (had to edit manualy proj file and add it there)

-Change from *.Controls.Map to *Map.Controls

– XAML definition

 <maps:Map x:Name="sampleMap"
 LandmarksEnabled="False"
 Loaded="sampleMap_Loaded"
 CartographicMode="Hybrid"                    
 Center="49.320574,16.68942"
 ZoomLevel="14" />

Loading TileSource:

private void sampleMap_Loaded(object sender, RoutedEventArgs e)
{
MapsSettings.ApplicationContext.ApplicationId = "<applicationid>";
MapsSettings.ApplicationContext.AuthenticationToken = "<authenticationtoken>";
TileSource wmsTileSource = new WMSTile();
sampleMap.TileSources.Add(wmsTileSource);
}

..then in your TileSource you can use as usual your method GetUri.

Sample code based on Microsoft Sample can be downloaded here: www.sumbera.com/lab/WP8/WMSonWP8.zip

WMS overlay in iOS MapKit

update May 2014: iOS7 version and notes available here: https://blog.sumbera.com/2014/05/17/wms-on-mapkit-with-ios7/

 

I have crafted really simple and quick code at ESA App dev camp for viewing WMS sources.  And as few people questioned  me on  how to do this, I am posting the full code of the MapView component that takes sample WMS service (Ozone) and overlays this above MapKit.

In github here : https://github.com/Sumbera/WMSoverMapKit

you will find MapViewController that accepts  WMS  sources stored as array with BBOX %f, %f, %f, %f

-(void) addWMSOverlays: (NSArray*) overlays

this can be called from your rootController:

MapViewController * mapViewController = [[MapViewController alloc] init];

WMSOverlay * wmsOverlay =[[WMSOverlay alloc]
 initWithName:@"Ozone" Url:@"http://wdc.dlr.de/ogc/produkt_t?LAYERS=GOME2_O3&
 TRANSPARENT=TRUE&
 FORMAT=image/png&
 STYLES=&
 SLD=http%3A%2F%2Fwdc.dlr.de%2Fsld%2FGOME2_O3_sld.xml&
 TIME=2012-02-12T00:00:00Z&
 SERVICE=WMS&
 VERSION=1.1.1&
 REQUEST=GetMap&
 SRS=EPSG:4326&
 WIDTH=256&
 HEIGHT=256" 
 Opacity:0.5];
  [mapViewController  addWMSOverlays:[NSArray arrayWithObjects:wmsOverlay, nil]];

please note:

#1. that sample code uses experimentaly MKNetworkKit, which has some occasional troubles. You can replace the download method in WMSOverlayView  class downloadTile

#2 it uses simple hash for storing tiles on cache.

enjoy