WMS Tile Layer?

267 views
Skip to first unread message

Matt Williamson

unread,
Aug 5, 2009, 5:49:57 PM8/5/09
to route-me
Hello list,

I'm very new to route-me, but I've done a lot of work with OpenLayers,
GMaps, and server-side stuff like Mapserver, GeoServer, etc...so I
think I'm ramping up pretty quickly, and have a few questions:

First, I need to be able to access a plain-old WMS service for a
custom tile source. I think I see how I could write a subclass of
RMAbstractMercatorWebSource to do this to get EPSG:900913 projected
images as tiles...but first, is anybody else already working on this,
or is there a better way you'd recommend going about it? Since I plan
to access several WMS layers, I'll be generalizing it, so I'll be
happy to contribute the code back, if you like it (though I'm still
relatively new to Objective-C, also, so no promises as to initial
quality).

Second, I need to be able to stack a second, transparent tile source
(PNG probably, but possibly transparent GIF) on top of the base map
(like Google's "hybrid" map). I think it looks like this is possible,
but is there some sample code around for it?

Thanks!

-Matt

Ivo Brodien

unread,
Aug 5, 2009, 6:33:27 PM8/5/09
to route-...@googlegroups.com
Hi,

that would be something really neat. I once started to add a
SVGWMSWebSource class to get svg layer from a WMS server (also
Geoserver) but i did never finishedand actually did not get really far
in have SVG tiles show up.

However, here is some code you might find helpfull. It converts tiles
to Lat lang bounds, which you might neat if your bounding box needs
lat lon values.

http://groups.google.com/group/route-me-map/browse_thread/thread/b6aa3757d46055aa/c93e7b0c861973e5?lnk=gst&q=LatLonToMeters%23c93e7b0c861973e5


Good Luck!
cheers
ivo

Ivo Brodien

unread,
Aug 5, 2009, 6:35:02 PM8/5/09
to route-...@googlegroups.com
Hi,

that would be something really neat. I once started to add a SVGWMSWebSource class to get svg layer from a WMS server (also Geoserver) but i did never finishedand actually did not get really far in havomg SVG tiles show up in route-me. In the end I stopped because i used just PNGs.


However, here is some code you might find helpfull. It converts tiles to Lat lang bounds, which you might neat if your bounding box needs lat lon values.

http://groups.google.com/group/route-me-map/browse_thread/thread/b6aa3757d46055aa/c93e7b0c861973e5?lnk=gst&q=LatLonToMeters%23c93e7b0c861973e5


Good Luck!
cheers
ivo
On 05.08.2009, at 23:49, Matt Williamson wrote:

Matt Williamson

unread,
Aug 6, 2009, 5:58:47 PM8/6/09
to route-...@googlegroups.com
Ivo,

Thanks a lot! That code was very helpful, but also drove me nuts all day, because I finally figured out that the original author didn't properly account for TMS-style Y-coordinates (ascending south-to-north). Finally got it right, though, and my current code is below.

Anyone else care to help with my second question? What I'm looking to do, eventually, is overlay weather radar data on top of a static base map, so I'm looking for how to use two tile sources, one on top of another. I see that the "Old Map App" does something very similar, so it must be possible...but how???

Thanks again, here's a snapshot of my generic WMS source. There's probably all kinds of memory leaks in it, since that's the part of Cocoa that I haven't figured out yet. Also needs properties for the key/name/description. But it works! If you'd like to get this merged into the trunk eventually, let me know, I don't know what the procedure is for committing.

-Matt


#import "RMGenericMercatorWMSSource.h"

CGFloat DegreesToRadians(CGFloat degrees) {return degrees * M_PI / 180;}; 
CGFloat RadiansToDegrees(CGFloat radians) {return radians * 180/ M_PI;}; 

@implementation RMGenericMercatorWMSSource

-(id) initWithBaseUrl:(NSString *)baseUrl parameters:(NSDictionary *)params
if (![super init]) 
return nil
initialResolution = 2 * M_PI * 6378137 / [[self class] tileSideLength];
// 156543.03392804062 for sideLength 256 pixels 
originShift = 2 * M_PI * 6378137 / 2.0;
// 20037508.342789244 


// setup default parameters
// use official EPSG:3857 by default, user can override to 900913 if needed.
wmsParameters = [[NSMutableDictionary alloc] initWithObjects:[[[NSArray alloc] initWithObjects:@"EPSG:3857",@"image/png",@"GetMap",@"1.1.1",@"WMS",nil] autorelease
  forKeys:[[[NSArray alloc] initWithObjects:@"SRS",@"FORMAT",@"REQUEST",@"VERSION",@"SERVICE",nil] autorelease]];
[wmsParameters addEntriesFromDictionary:params];

// build WMS request URL template
urlTemplate = [NSString stringWithString:baseUrl];
NSEnumerator *e = [wmsParameters keyEnumerator];
NSString *key;
NSString *delimiter = @"";
while (key = [e nextObject]) {
urlTemplate = [urlTemplate stringByAppendingFormat:@"%@%@=%@",
  delimiter,
  [[key uppercaseString] stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding], 
  [[wmsParameters objectForKey:key] stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]];
delimiter = @"&";
}
int sideLength =  [[self class] tileSideLength];
urlTemplate = [[urlTemplate stringByAppendingFormat:@"&WIDTH=%d&HEIGHT=%d",sideLength,sideLength] retain];
return self;
}


-(NSString*) tileURL: (RMTile) tile 
//RMLatLongBounds tileBounds = [self TileLatLonBounds:tile];
// Get BBOX coordinates in meters
CGXYRect tileBounds = [self TileBounds:tile];


NSString *url = [urlTemplate stringByAppendingFormat:@"&BBOX=%f,%f,%f,%f",
tileBounds.ul.x,
tileBounds.lr.y,
tileBounds.lr.x,
tileBounds.ul.y];
NSLog(@"Tile %d,%d,%d yields %@",tile.zoom, tile.x, tile.y, url); 
return url; 

// implement in subclass?
-(NSString*) uniqueTilecacheKey
{
return @"AbstractMercatorWMSSource";
}

-(NSString *)shortName
{
return @"Generic WMS Source";
}
-(NSString *)longDescription
{
return @"Generic WMS Source";
}
-(NSString *)shortAttribution
{
return @"Generic WMS Source";
}
-(NSString *)longAttribution
{
return @"Generic WMS Source";
}

-(float) minZoom
{
return 1.0f;
}
-(float) maxZoom
{
return 21.0f;
}



// Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:3857 
-(CGPoint) LatLonToMeters: (CLLocationCoordinate2D) latlon 
CGPoint meters; 
meters.x = latlon.longitude * originShift / 180.0
meters.y = (log( tan((90.0 + latlon.latitude) * M_PI / 360.0 )) / (M_PI / 180.0)) * originShift / 180.0
return meters; 
}

//Converts XY point from Spherical Mercator EPSG:3857 to lat/lon in WGS84 Datum 
-(CLLocationCoordinate2D) MetersToLatLon: (CGPoint) meters 
CLLocationCoordinate2D latlon; 
latlon.longitude = (meters.x / originShift) * 180.0
latlon.latitude = (meters.y / originShift) * 180.0
//latlon.latitude = - 180 / M_PI * (2 * atan( exp( latlon.latitude * M_PI / 180.0)) - M_PI / 2.0); 
latlon.latitude = 180 / M_PI * (2 * atan( exp( latlon.latitude * M_PI / 180.0)) - M_PI / 2.0); 
return latlon; 

// Converts pixel coordinates in given zoom level of pyramid to EPSG:3857 
-(CGPoint) PixelsToMeters: (int) px PixelY:(int)py atZoom:(int)zoom 
float resolution = [self ResolutionAtZoom: zoom]; 
CGPoint meters; 
meters.x = px * resolution - originShift
meters.y = py * resolution - originShift
return meters; 

//Returns bounds of the given tile in EPSG:3857 coordinates 
-(CGXYRect)  TileBounds: (RMTile) tile 
{
int sideLength =  [[self class] tileSideLength];

int zoom = tile.zoom;
long twoToZoom = pow(2,zoom);
CGXYRect tileBounds; 
tileBounds.ul = [self PixelsToMeters: (tile.x * sideLength) 
  PixelY: ((twoToZoom-tile.y) * sideLength) 
  atZoom: zoom ]; 
tileBounds.lr = [self PixelsToMeters: ((tile.x+1) * sideLength) 
  PixelY: ((twoToZoom-tile.y-1) * sideLength) 
  atZoom: zoom];
return tileBounds; 

//Resolution (meters/pixel) for given zoom level (measured at Equator) 
-(float) ResolutionAtZoom : (int) zoom 
return initialResolution / pow(2,zoom); 


@end

Anthony

unread,
Aug 10, 2009, 1:07:20 PM8/10/09
to route-me
Hello William!

Thanks a lot for your code, it help me a lot!

I still have some issue when i want to implement your code...

Can you send your code?

Thanks

Tony
> >http://groups.google.com/group/route-me-map/browse_thread/thread/b6aa...

Matt Williamson

unread,
Aug 12, 2009, 5:44:33 PM8/12/09
to route-...@googlegroups.com
Tony,

Glad to help! I can show you some example code for using the
RMGenericMercatorWMSSource, but I'm not sure exactly what you need.
The short answer is, here's how you might add a WMS source to a route-
me map:

// Alaskan mapping service (test)
NSDictionary *wmsParameters = [
[NSDictionary alloc]
initWithObjects:[NSArray arrayWithObjects:@"EPSG:
54004",@"",@"BestDataAvailableLayer",nil]
forKeys:[NSArray arrayWithObjects:@"SRS",@"styles",@"layers",nil]
];
id myTilesource = [[[RMAbstractMercatorWMSSource alloc]
initWithBaseUrl:@"http://wms.alaskamapped.org/cgi-bin/bdl_nl.cgi?"
parameters:wmsParameters] autorelease];
RMMapContents *contents = [[[RMMapContents alloc] initWithView:mapView
tilesource:myTilesource
centerLatLon:firstLocation
zoomLevel:10.0f
maxZoomLevel:11.0f
minZoomLevel:1.0f
backgroundImage:nil] autorelease];

This example uses an accessible WMS layer I found online, but its
projection is EPSG:54004...which sort-of works for imagery display,
but markers wouldn't be georeferenced properly, for example. You'll
need to have a WMS source that will output imagery in EPSG:900913 or
EPSG:3857 for this to really work properly.

But the idea is, you make a wmsParameters dictionary with the
appropriate query-string parameters to pass to your WMS service. Then
instantiate the RMGenericMercatorWMSSource object with the base URL
and the parameter dictionary. Then add the tilesource to your
RMMapContents object (like you would with any other non-default
tilesource).

If you need help setting up a WMS service to serve the imagery, that's
far beyond the scope of the list here.

I've also attached my .m and .h file for the class (assuming the
listserver will let me attach files?), in case that's what you needed.

-Matt


RMGenericMercatorWMSSource.h
RMGenericMercatorWMSSource.m
Reply all
Reply to author
Forward
0 new messages