def register_layer(self, layer, defaultstyle, extrastyles=()): layername = layer.name if not layername: raise ServerConfigurationError('Attempted to register an unnamed layer.') if not layer.wms_srs and not re.match('^\+init=epsg:\d+$', layer.srs) and not re.match('^\+proj=.*$', layer.srs): raise ServerConfigurationError('Attempted to register a layer without an epsg projection defined.') if defaultstyle not in self.styles.keys() + self.aggregatestyles.keys(): raise ServerConfigurationError('Attempted to register a layer with an non-existent default style.') layer.wmsdefaultstyle = defaultstyle if isinstance(extrastyles, tuple): for stylename in extrastyles: if type(stylename) == type(''): if stylename not in self.styles.keys() + self.aggregatestyles.keys(): raise ServerConfigurationError('Attempted to register a layer with an non-existent extra style.') else: ServerConfigurationError('Attempted to register a layer with an invalid extra style name.') layer.wmsextrastyles = extrastyles else: raise ServerConfigurationError('Layer "%s" was passed an invalid list of extra styles. List must be a tuple of strings.' % layername) layerproj = common.Projection(layer.srs) env = getattr(layer, 'maximum_extent', layer.envelope()) llp = layerproj.inverse(Coord(env.minx, env.miny)) urp = layerproj.inverse(Coord(env.maxx, env.maxy)) if self.latlonbb is None: self.latlonbb = Box2d(llp, urp) else: self.latlonbb.expand_to_include(Box2d(llp, urp)) self.ordered_layers.append(layer) self.layers[layername] = layer
def loadXML(self, xmlfile=None, strict=False, xmlstring='', basepath=''): """Loads and stores a complete map definition in leiu of a set of layers. Stores an empty style of the same name, which is the only option for rendering. The map will be rendered as prescribed in the XML.""" config = ConfigParser.SafeConfigParser() map_wms_srs = None if self.configpath: config.readfp(open(self.configpath)) if config.has_option('map', 'wms_srs'): map_wms_srs = config.get('map', 'wms_srs') tmp_map = Map(0,0) if xmlfile: load_map(tmp_map, xmlfile, strict) elif xmlstring: load_map_from_string(tmp_map, xmlstring, strict, basepath) else: raise ServerConfigurationError("Mapnik configuration XML is not specified - 'xmlfile' and 'xmlstring' variables are empty.\ Please set one of this variables to load mapnik map object.") # parse map level attributes if tmp_map.background: self.map_attributes['bgcolor'] = tmp_map.background if tmp_map.buffer_size: self.map_attributes['buffer_size'] = tmp_map.buffer_size if xmlfile is None : # Map objects have no name, so just call it default. layer_name = 'default' else : # The layer name is the basename of the xml file or the # whole file name layer_name = xmlfile fname_match = re.match('([A-Za-z0-9_\-\.]+).xml', xmlfile) if fname_match is not None : layer_name = fname_match.group(1) else : layer_name = xmlfile style_name = layer_name style_obj = Style() # Make the map have attributes expected of layers tmp_map.name = layer_name map_p = common.Projection(tmp_map.srs) if map_wms_srs is None : tmp_map.wms_srs = map_p.epsgstring() else : tmp_map.wms_srs = map_wms_srs tmp_map.queryable = False # set map extent from config file. geog_coords = config.get('map','wms_extent').split(',') geog_box = Box2d(float(geog_coords[0]), float(geog_coords[1]), float(geog_coords[2]), float(geog_coords[3])) proj_box = geog_box.forward(map_p) tmp_map.zoom_to_box(proj_box) tmp_map.maximum_extent = proj_box self.register_style(style_name, style_obj) self.register_layer(tmp_map, style_name)
def tile_generator(zoom_lvl, tile_width, tile_height, style_sheet, target_storage_path): print('\n\t[+]Generating tiles in zoom level -- {}\n'.format(zoom_lvl)) tiles = get_tile_extent(zoom_lvl, (-180, +90), tile_width, tile_height, 360, 180) if (not tiles): return False for key, value in tiles.items(): map_obj = Map(tile_width, tile_height, '+proj=longlat +datum=WGS84 +no_defs ') if (not format_style_sheet(style_sheet, [ 'layer1', 'layer2', 'layer3', 'layer4', 'layer5', 'layer6' ], 'Parameter', ['name', 'extent'], '{}, {}, {}, {}'.format(*value), 'tmp.xml')): return False print('\t\t[+]Rendering tile {} - {} - {} ...'.format( zoom_lvl, *key.split(','))) load_map(map_obj, 'tmp.xml') # loads generated XML style sheet map_obj.zoom_to_box(Box2d(*value)) render_to_file( map_obj, join(target_storage_path, '{}_{}_{}.png'.format(zoom_lvl, *key.split(','))), 'png256') # removes temporary XML file, which was generated from template XML unlink('tmp.xml') return True
def getPixelTileEnv(x, y, ntiles=1, includeBorder=True): """Returns the OSM Pixel coordinate Box2d for the tile(s) for the specified tile(s).""" border = 0 size = TILE_SIZE * ntiles if includeBorder: border = BORDER_WIDTH return Box2d(x * size - border, y * size - border, (x + 1) * size + border, (y + 1) * size + border)
def getTiles(envLL): """Gets the (basename, Box2d) of all (existing) 1/3 NED tiles to cover the specified Box2d""" fromx = int(floor(envLL.minx)) tox = int(floor(envLL.maxx)) fromy = int(ceil(envLL.miny)) toy = int(ceil(envLL.maxy)) tiles = [] for y in range(fromy, toy + 1): for x in range(fromx, tox + 1): basename = 'n%02dw%03d' % (y, -x) tilepath = getTilepath(basename) print tilepath if path.isfile(tilepath): tiles.append((basename, Box2d(x, y - 1, x + 1, y))) return tiles
def __init__(self, content): """ With MapRenderer you can render an aktionskarten map in different file formats like pdf, svg or png. Internally it uses mapnik and cairo to archieve this. A valid style has to be defined through `MAPNIK_OSM_XML`. Normally this a carto derived `style.xml`. In this file your datasource is specified. This can be for instance a postgres+postgis database with imported osm data. :param content: dict of map content """ # Mapnik uses mercator as internal projection. Our data is encoded in # latlon. Therefor we need a transformer for coordindates from longlat # to mercator proj_merc = Projection('+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0\ +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m \ +nadgrids=@null +no_defs +over') proj_longlat = Projection('+proj=longlat +ellps=WGS84 +datum=WGS84 \ +no_defs') transformer = ProjTransform(proj_longlat, proj_merc) # our maps should be printable on a DIN A4 page with 150dpi self._map = Map(1754, 1240) bbox = transformer.forward(Box2d(*content['bbox'])) self._map.zoom_to_box(bbox) self._map.buffer_size = 5 start = timer() # add osm data (background) load_map(self._map, current_app.config['MAPNIK_OSM_XML']) mid = timer() self._add_grid(content['grid']) self._add_features(content['features']) self._add_legend(content['name'], content['place'], content['datetime'], content['attributes']) end = timer() print("Map.init - OSM: ", mid - start) print("Map.init - Map: ", end - mid) print("Map.init - Total: ", end - start)
def __call__(self, layers, system): request = system['request'] # get image width and height width = 256 height = 256 # get image bbox z = int(request.matchdict['z']) x = int(request.matchdict['x']) y = int(request.matchdict['y']) step = max / (2**(int(z) - 1)) xmin = x * step - max ymin = max - y * step xmax = (x + 1) * step - max ymax = max - (y + 1) * step bbox = Box2d(xmin, ymax, xmax, ymin) m = Map(width, height) load_map(m, abspath_from_asset_spec('osmtm:views/map.xml')) for l in layers: m.layers.append(l) m.zoom_to_box(bbox) format = request.matchdict['format'] if format == 'png': im = Image(width, height) render(m, im, 1, 1) request.response_content_type = 'image/png' return im.tostring('png') elif format == 'json': grid = Grid(width, height) render_layer(m, grid, layer=0, fields=['id']) utfgrid = grid.encode('utf', resolution=4) return json.dumps(utfgrid)
#!/usr/bin/python from mapnik import Box2d # Misc test areas WA = Box2d(-125.3, 45.4, -116.8, 49.1) WAdetail = Box2d(-123.4, 46.2, -120.0, 48.1) Seattle = Box2d(-122.4, 47.5, -122.2, 47.7) WAnw = Box2d(-125.3, 47.7, -121, 49.1) WAne = Box2d(-121, 47.7, -116.8, 49.1) WAsw = Box2d(-125.3, 45.4, -121, 47.7) WAse = Box2d(-121, 45.4, -116.8, 47.7) NEdetail = Box2d(-71.5, 42, -70.5, 43) Stow = Box2d(-71.55, 42.40, -71.46, 42.47) BostonSS = Box2d(-71.2, 42.0, -70.6, 42.4) BostonDetail = Box2d(-71.11, 42.30, -70.99, 42.41) COdetail = Box2d(-105.1, 38.7, -104.7, 39.0) COminor = Box2d(-105.0, 38.8, -104.8, 38.95) NEdetail1 = Box2d(-71.0, 42.0, -70.5, 42.5) NEdetail2 = Box2d(-71.0, 42.5, -70.5, 43.0) NEdetail3 = Box2d(-71.5, 42.0, -71.0, 42.5) NEdetail4 = Box2d(-71.5, 42.5, -71.0, 43.0) ca = Box2d(-124.9, 32.3, -113.9, 42.1) cadetail = Box2d(-123, 37, -119.0, 38.0) sfo = Box2d(-122.6, 37.6, -122.2, 37.9) oakland = Box2d(-122.34, 37.75, -122.12, 37.89) yosdetail = Box2d(-119.67, 37.70, -119.52, 37.76) AbandonedRailwayRelation = Box2d(-76.660, 39.196, -76.652, 39.207)
def __init__(self, template, sort_key=None, clipped='true', zoom_data='single'): ''' Make a new Datasource. Parameters: template: Required URL template with placeholders for tile zoom, x and y, e.g. "http://example.com/layer/{z}/{x}/{y}.json". sort_key: Optional field name to use when sorting features for rendering. E.g. "name" or "name ascending" to sort ascending by name, "name descending" to sort descending by name. clipped: Optional boolean flag to determine correct behavior for duplicate geometries. When tile data is not clipped, features() will check geometry uniqueness and throw out duplicates. Setting clipped to false for actually-clipped geometries has no effect but wastes time. Setting clipped to false for unclipped geometries will result in possibly wrong-looking output. Default is "true". zoom_data: Optional keyword specifying single or double zoom data tiles. Works especially well with relatively sparse label layers. When set to "double", tiles will be requested at one zoom level out from the map view, e.g. double-sized z13 tiles will be used to render a normal z14 map. Default is "single". ''' scheme, host, path, p, query, f = urlparse(template) self.host = host self.port = 443 if scheme == 'https' else 80 if ':' in host: self.host = host.split(':', 1)[0] self.port = int(host.split(':', 1)[1]) self.path = path + ('?' if query else '') + query self.path = self.path.replace('%', '%%') self.path = self.path.replace('{Z}', '{z}').replace('{z}', '%(z)d') self.path = self.path.replace('{X}', '{x}').replace('{x}', '%(x)d') self.path = self.path.replace('{Y}', '{y}').replace('{y}', '%(y)d') if sort_key is None: self.sort, self.reverse = None, None elif sort_key.lower().endswith(' descending'): logging.debug('Will sort by %s descending' % sort_key) self.sort, self.reverse = sort_key.split()[0], True else: logging.debug('Will sort by %s ascending' % sort_key) self.sort, self.reverse = sort_key.split()[0], False self.clipped = clipped.lower() not in ('false', 'no', '0') self.zoom_adjust = {'double': 1}.get(zoom_data.lower(), 0) bbox = Box2d(-diameter / 2, -diameter / 2, diameter / 2, diameter / 2) PythonDatasource.__init__(self, envelope=bbox)
def _render(value, system): request = system['request'] if not isinstance(value, tuple): value = (None, value) layer_name, collection = value if not hasattr(collection, 'features'): raise ValueError('renderer is not passed a feature collection') # get image width and height try: img_width = int(request.params.get('img_width', 256)) except: request.response_status = 400 return 'incorrect img_width' try: img_height = int(request.params.get('img_height', 256)) except: request.response_status = 400 return 'incorrect img_height' # get image format try: img_format = request.params.get( 'img_format', request.matchdict.get('format', 'png')) img_format = str(img_format) except: request.response_status = 400 return 'incorrect img_format' # get image bbox img_bbox = request.params.get('img_bbox', request.params.get('bbox')) if img_bbox: try: img_bbox = map(float, img_bbox.split(',')) except ValueError: request.response_status = 400 return 'incorrect img_bbox' img_bbox = Box2d(*img_bbox) m = Map(img_width, img_height) load_map(m, mapfile) if len(m.layers) == 0: raise ValueError('no layer in the mapnik map') # if no layer_name is provided then, by convention, use # the first layer in the mapnik map if layer_name is None: layer_name = m.layers[0].name layer = self._set_layer_in_map(m, layer_name) layer.datasource = self._create_datasource(collection) m.zoom_to_box(img_bbox or layer.envelope()) im = Image(img_width, img_height) render(m, im, 1, 1) # get the image format from the request request.response.content_type = 'image/%s' % img_format return im.tostring(img_format)
if __name__ == "__main__": mapfile = "my_styles/mapnik2normal.xml" map_uri = "map.png" lat = 49.25 lon = 7.0 if len(sys.argv) == 2: zoom = int(sys.argv[1]) else: zoom = 13 imgx = 500 imgy = 400 ll = center_to_bbox(lat, lon, zoom, imgx, imgy) m = Map(imgx, imgy) load_map(m, mapfile) prj = Projection( "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m" " +nadgrids=@null +no_defs +over") c0 = prj.forward(Coord(ll[0], ll[1])) c1 = prj.forward(Coord(ll[2], ll[3])) bbox = Box2d(c0.x, c0.y, c1.x, c1.y) m.zoom_to_box(bbox) im = Image(imgx, imgy) render(m, im) view = im.view(0, 0, imgx, imgy) # x,y,width,height view.save(map_uri, 'png')
transform = ProjTransform(latlon, merc) if len(sys.argv) == 3: ll = transform.forward(Coord(float(sys.argv[1]), float(sys.argv[2]))) if (ll.y > max_3785): print(' '.join([str(ll.x), str(max_3785)])) elif (ll.y < -max_3785): print(' '.join([str(ll.x), str(-max_3785)])) else: print(' '.join([str(ll.x), str(ll.y)])) elif len(sys.argv) == 5: minx, miny, maxx, maxy = (float(sys.argv[1]), float(sys.argv[2]), float(sys.argv[3]), float(sys.argv[4])) if (miny < -max_4326): miny = -max_4326 if (maxy > max_4326): maxy = max_4326 bbox = transform.forward(Box2d(minx, miny, maxx, maxy)) print(' '.join( [str(bbox.minx), str(bbox.miny), str(bbox.maxx), str(bbox.maxy)])) else: print("Error: Incorrect number of arguments")
def _buildMap(self, params): """This variant of buildmap just serves up the map as defined in the xml file, plus some customizations from the params. Style and SRS are ignored.""" if str(params['crs']) not in self.allowedepsgcodes: raise OGCException( 'Unsupported CRS "%s" requested.' % str(params['crs']).upper(), 'InvalidCRS') if params['bbox'][0] >= params['bbox'][2]: raise OGCException( "BBOX values don't make sense. minx is greater than maxx.") if params['bbox'][1] >= params['bbox'][3]: raise OGCException( "BBOX values don't make sense. miny is greater than maxy.") # There should be only one layer requested. if len(params['layers']) <> 1: raise OGCException( "When specifying a pre-rendered map, only one layer is allowed." ) # retrieve the map object and set width and height. # leave srs alone. m = self.mapfactory.layers[params['layers'][0]] m.width = params['width'] m.height = params['height'] transparent = params.get('transparent', '').lower() == 'true' # disable transparent on incompatible formats if transparent and params.get('format', '') == 'image/jpeg': transparent = False if transparent: # transparent has highest priority pass elif params.has_key('bgcolor'): # if not transparent use bgcolor in url m.background = params['bgcolor'] else: # if not bgcolor in url use map background if mapnik_version() >= 200000: bgcolor = self.mapfactory.map_attributes.get('bgcolor', None) else: bgcolor = self.mapfactory.map_attributes.get( 'background-color', None) if bgcolor: m.background = bgcolor else: # if not map background defined use white color m.background = Color(255, 255, 255, 255) if params.has_key('buffer_size'): if params['buffer_size']: m.buffer_size = params['buffer_size'] else: buffer_ = self.mapfactory.map_attributes.get('buffer_size') if buffer_: m.buffer_size = self.mapfactory.map_attributes['buffer_size'] m.zoom_to_box( Box2d(params['bbox'][0], params['bbox'][1], params['bbox'][2], params['bbox'][3])) return m
def _buildMap(self, params): if str(params['crs']) not in self.allowedepsgcodes: raise OGCException( 'Unsupported CRS "%s" requested.' % str(params['crs']).upper(), 'InvalidCRS') if params['bbox'][0] >= params['bbox'][2]: raise OGCException( "BBOX values don't make sense. minx is greater than maxx.") if params['bbox'][1] >= params['bbox'][3]: raise OGCException( "BBOX values don't make sense. miny is greater than maxy.") # relax this for now to allow for a set of specific layers (meta layers even) # to be used without known their styles or putting the right # of commas... #if params.has_key('styles') and len(params['styles']) != len(params['layers']): # raise OGCException('STYLES length does not match LAYERS length.') m = Map(params['width'], params['height'], '+init=%s' % params['crs']) transparent = params.get('transparent', '').lower() == 'true' # disable transparent on incompatible formats if transparent and params.get('format', '') == 'image/jpeg': transparent = False if transparent: # transparent has highest priority pass elif params.has_key('bgcolor'): # if not transparent use bgcolor in url m.background = params['bgcolor'] else: # if not bgcolor in url use map background if mapnik_version() >= 200000: bgcolor = self.mapfactory.map_attributes.get('bgcolor', None) else: bgcolor = self.mapfactory.map_attributes.get( 'background-color', None) if bgcolor: m.background = bgcolor else: # if not map background defined use white color m.background = Color(255, 255, 255, 255) if params.has_key('buffer_size'): if params['buffer_size']: m.buffer_size = params['buffer_size'] else: buffer_ = self.mapfactory.map_attributes.get('buffer_size') if buffer_: m.buffer_size = self.mapfactory.map_attributes['buffer_size'] # haiti spec tmp hack! show meta layers without having # to request huge string to avoid some client truncating it! if params['layers'] and params['layers'][0] in ( 'osm_haiti_overlay', 'osm_haiti_overlay_900913'): for layer_obj in self.mapfactory.ordered_layers: layer = copy_layer(layer_obj) if not hasattr(layer, 'meta_style'): pass else: layer.styles.append(layer.meta_style) m.append_style( layer.meta_style, self.mapfactory.meta_styles[layer.meta_style]) m.layers.append(layer) # a non WMS spec way of requesting all layers # uses orderedlayers that preserves original ordering in XML mapfile elif params['layers'] and params['layers'][0] == '__all__': for layer_obj in self.mapfactory.ordered_layers: # if we don't copy the layer here we get # duplicate layers added to the map because the # layer is kept around and the styles "pile up"... layer = copy_layer(layer_obj) if hasattr(layer, 'meta_style'): continue reqstyle = layer.wmsdefaultstyle if reqstyle in self.mapfactory.aggregatestyles.keys(): for stylename in self.mapfactory.aggregatestyles[reqstyle]: layer.styles.append(stylename) else: layer.styles.append(reqstyle) for stylename in layer.styles: if stylename in self.mapfactory.styles.keys(): m.append_style(stylename, self.mapfactory.styles[stylename]) m.layers.append(layer) else: for layerindex, layername in enumerate(params['layers']): if layername in self.mapfactory.meta_layers: layer = copy_layer(self.mapfactory.meta_layers[layername]) layer.styles.append(layername) m.append_style(layername, self.mapfactory.meta_styles[layername]) else: try: # uses unordered dict of layers # order based on params['layers'] request which # should be originally informed by order of GetCaps response layer = copy_layer(self.mapfactory.layers[layername]) except KeyError: raise OGCException( 'Layer "%s" not defined.' % layername, 'LayerNotDefined') try: reqstyle = params['styles'][layerindex] except IndexError: reqstyle = '' if len(layer.wmsextrastyles) > 1 and reqstyle == 'default': reqstyle = '' if reqstyle and reqstyle not in layer.wmsextrastyles: raise OGCException( 'Invalid style "%s" requested for layer "%s".' % (reqstyle, layername), 'StyleNotDefined') if not reqstyle: reqstyle = layer.wmsdefaultstyle if reqstyle in self.mapfactory.aggregatestyles.keys(): for stylename in self.mapfactory.aggregatestyles[ reqstyle]: layer.styles.append(stylename) else: layer.styles.append(reqstyle) for stylename in layer.styles: if stylename in self.mapfactory.styles.keys(): m.append_style(stylename, self.mapfactory.styles[stylename]) else: raise ServerConfigurationError( 'Layer "%s" refers to non-existent style "%s".' % (layername, stylename)) m.layers.append(layer) m.zoom_to_box( Box2d(params['bbox'][0], params['bbox'][1], params['bbox'][2], params['bbox'][3])) return m
def envPixelToLL(self, env, zoom): lb = self.pixelToLL(Coord(env.minx, env.miny), zoom) rt = self.pixelToLL(Coord(env.maxx, env.maxy), zoom) return Box2d(lb.x, lb.y, rt.x, rt.y)
def envLLToPixel(self, env, zoom): lb = self.LLToPixel(Coord(env.minx, env.miny), zoom) rt = self.LLToPixel(Coord(env.maxx, env.maxy), zoom) return Box2d(lb.x, lb.y, rt.x, rt.y)