def _CheckLayers(self, layers_text): """Checks if the requested layer is available.""" logger.debug("Processing the target layer") layer_names = layers_text.split(",") if 1 < len(layer_names): # We only handle a single layer at a time (jeffdonner is not sure # what handling multiple ones would mean - compositing them per # STYLES perhaps? Sending them in sequence, somehow?) logger.warning("Received request for multiple layers. " "We only handle one at a time, the first.") # Just check the first, that's all we'll use. layer_name = layer_names[0] server_layers_by_name = self.layer_obj.GetLayers( utils.GetValue(self.parameters, "server-url"), utils.GetValue(self.parameters, "TargetPath")) self.requested_layer_obj = server_layers_by_name.get(layer_name, None) if not self.requested_layer_obj: logger.error("Layer %s doesn't exist", layer_name) self._ServiceExceptionImpl( _LAYER_NOT_DEFINED, "No layer matching \'%s\' found" % layer_name) # By this point we know it's there. logger.debug("Target layer: " + layer_name)
def _ExtractLayerObj(self, layer_name): server_layers_by_name = self.layer_obj.GetLayers( utils.GetValue(self.parameters, "server-url"), utils.GetValue(self.parameters, "TargetPath")) logger.debug("Server layers: %s ", pformat(server_layers_by_name)) layer_obj = server_layers_by_name.get(layer_name, None) return layer_obj
def GenerateOutput(self): """Executes the operation and returns the image. Returns: The image composed of tiles. """ logger.debug("Generating GetMapRequest response " "for WMS request for version 1.3.0") # If "TargetPath" query parameter doesn't exist in the # input parameters, then send back "ServiceException" # to the client. target_path = utils.GetValue(self.parameters, "TargetPath") if not target_path: headers = _HEADERS_EXCEPTION response = ServiceException(None, "Target path is not specified.").Xml() return headers, response else: try: self._ProcessResponse() except ServiceException, e: headers = _HEADERS_EXCEPTION return headers, e.Xml()
def GenerateOutput(self): """Generate response for GetCapabilities request. Returns: headers: List of headers for the response. response: GetCapabilities response. """ logger.debug("Processing GetCapabilities response for WMS v1.3.0") # If "TargetPath" query parameter doesn't exist in the # input parameters, then send back "ServiceException" # to the client. target_path = utils.GetValue(self.parameters, "TargetPath") if not target_path: headers = _HEADERS_EXCEPTION response = ServiceException(None, "Target path is not specified.").Xml() else: try: headers = [ ("Content-Disposition", 'inline; filename="wmsCapabilities-%s-google.xml"' % _WMS_VERSION), ("Content-Type", _XML_CONTENT_TYPE) ] response = self._Xml() except ServiceException, e: headers = _HEADERS_EXCEPTION return headers, e.Xml()
def IsValidService(self, parameters): """Checks if the service is valid. Args: parameters: All of the query parameters (SERVICE, FORMAT etc), with the additions of this-endpoint. Returns: is_valid_service: If the service is valid. msg_to_client: Message sent back to the client. """ logger.debug("Checking if the service is a valid one.") is_valid_service = False service = utils.GetValue(parameters, "service") request_type = utils.GetValue(parameters, "request") if service not in _SERVICES: if service is None: if request_type == "GetCapabilities": # SERVICE parameter is mandatory for GetCapabilities request. logger.error("No service parameter in the WMS request") msg_to_client = ("MISSING SERVICE PARAMETER " "(expected something like WMS)") elif request_type == "GetMap": # SERVICE parameter is optional for GetMap request. is_valid_service = True msg_to_client = "VALID SERVICE PARAMETER RECEIVED" else: # Missing or no parameters in the WMS request. logger.error("Invalid WMS request received") msg_to_client = "INVALID WMS REQUEST RECEIVED" else: logger.error("Unknown service in the WMS request: \'%s\'", service) msg_to_client = "BAD SERVICE PARAMETER (expected something like WMS)" else: is_valid_service = True msg_to_client = "VALID SERVICE PARAMETER RECEIVED" logger.debug("Valid service parameter received: \'%s\'", service) return is_valid_service, msg_to_client
def _EnsureRequiredParameters(self): """Mechanically produces a ServiceException if a required parm. is missing. Checks if the required parameter is available and is non-empty. """ for reqd in self.required_param_names: # GIS clients send an empty value for "styles" parameter by default. # Empty values for styles parameter should be accepted. if reqd == "styles": continue if utils.GetValue(self.parameters, reqd) is None: error_message = "Missing required parameter: \'%s\'" % reqd logger.debug(error_message) self._ServiceException(None, error_message)
def IsValidVersion(self, parameters): """Checks if the version is a valid one. Args: parameters: All of the query parameters (SERVICE, FORMAT etc), with the additions of this-endpoint. Returns: is_valid_version: If the version is valid. msg_to_client: Message sent back to the client. version_handler: Version handler appriopriate to the WMS version. """ logger.debug("Checking if the WMS version is supported.") is_valid_version = False version = utils.GetValue(parameters, "version") # TODO: Add support for 1.0.7, 1.1.0 and others. # The MapServer code has good examples of handling the minor # differences between versions. if version is None or version.startswith("1.3."): # Tolerate 1.3.x for no reason. # If there is no version parameter, we're supposed to reply with # the highest we support, ie 1.3.0 version = _VERSION_V130 # We cheat here - we don't use this version yet in the # supposedly common handling, but this lets us. parameters["version"] = _VERSION_V130 version_handler = _SERVICE_HANDLERS.get(version, None) if version_handler is None: # Have a version, but it's something other than we support. # We can't return a ServiceException because we don't know what # format to send it in - it changes between versions. msg_to_client = "Version %s not supported" % version logger.error("WMS Version not supported: \'%s\'", version) else: is_valid_version = True msg_to_client = "Version %s supported" % version logger.debug("WMS Version supported: \'%s\'", version) return is_valid_version, msg_to_client, version_handler
def _CheckParameters(self): """Checks if required parameters are available in the request.""" self._EnsureRequiredParameters() # presence is established - now we're looking in more detail. lite_checkers = { # version already checked by this point # request already checked by this point "layers": self._CheckLayers, "styles": self._CheckStyles, "crs": self._CheckCrs, "srs": self._CheckSrs, "bbox": self._CheckBbox, "width": self._CheckWidth, "height": self._CheckHeight, "format": self._CheckFormat } for name, checker in lite_checkers.iteritems(): parameter_value = utils.GetValue(self.parameters, name) checker(parameter_value)
def GetRequestHandler(self, parameters, version_handler): """Get the request handler appropriate to the WMS request. Args: parameters: All of the query parameters (SERVICE, FORMAT etc), with the additions of 'this-endpoint'. version_handler: Version handler appropriate to the WMS version. Returns: req_hdlr_obj: Request handler. """ req_hdlr_obj = None request_name = utils.GetValue(parameters, "request") request_handler = version_handler.get(request_name, None) if request_handler is None: req_hdlr_obj = version_handler["bad_service_handler"](request_name) else: req_hdlr_obj = request_handler(self.layer_obj, parameters) logger.debug("Type of WMS request: %s", type(req_hdlr_obj)) return req_hdlr_obj
def SetLayers(self): """Set the layers for the capabilities xml.""" # This outer, inaccessible layer is to give information just once; # the sub-layers inherit it. outer_layer = capabilities_wms.Layer( # 7.2.4.7.4 The layer is area-filling => opaque opaque=True, # 7.2.4.7.5 -- we can subset. noSubsets=False, # whether we support GetFeatureInfo queryable=False, # -- can't request it, this is just a container. Name=None, Title=_TITLE) server_layers_by_name = self.layer_obj.GetLayers( utils.GetValue(self.parameters, "server-url"), utils.GetValue(self.parameters, "TargetPath")) if not server_layers_by_name: # Raise ServiceException here. raise ServiceException(None, "Database type is not supported.") for layer_name, server_layer in server_layers_by_name.iteritems(): proj = server_layer.projection wms_layer = capabilities_wms.Layer( # 7.2.4.7.4 - Even for vector maps we always get data from # the server, even if it's just a transparent tile. By # jeffdonner's reading, this means that even the vector # layers are 'opaque'. opaque=True, # 7.2.4.7.5 - we can subset. noSubsets=False, queryable=False, Name=layer_name, Title=server_layer.label, # ex geo bounding box is required. EX_GeographicBoundingBox=capabilities_wms. EX_GeographicBoundingBox( westBoundLongitude=-proj.MAX_LONGITUDE, eastBoundLongitude=proj.MAX_LONGITUDE, southBoundLatitude=-proj.MAX_LATITUDE, northBoundLatitude=proj.MAX_LATITUDE)) for epsg_name in proj.EPSG_NAMES: wms_layer.add_CRS(epsg_name) map_limits = proj.AdvertizedLogOuterBounds() bounding_boxes = [] for epsg_name in proj.EPSG_NAMES: (min_x, min_y, max_x, max_y) = self._GetMapLimitsForEpsg(map_limits, epsg_name) bounding_box_object = capabilities_wms.BoundingBox( CRS=epsg_name, minx=min_x, miny=min_y, maxx=max_x, maxy=max_y) bounding_boxes.append(bounding_box_object) wms_layer.set_BoundingBox(bounding_boxes) outer_layer.add_Layer(wms_layer) self.capabilities_xml.get_Capability().set_Layer(outer_layer)
def SetLayers(self): """Set the layers for the capabilities xml.""" # This outer, inaccessible layer is to give information just once; # the sub-layers inherit it. outer_layer = capabilities_wms.Layer( # If this is a containing layer it should be opaque=0 opaque=0, cascaded=None, fixedHeight=None, fixedWidth=None, # 7.1.4.6.4 - we obviously can subset. noSubsets=0, queryable=0, # This is a containing layer. Name=None, Title=capabilities_wms.Title(_TITLE)) server_layers_by_name = self.layer_obj.GetLayers( utils.GetValue(self.parameters, "server-url"), utils.GetValue(self.parameters, "TargetPath")) if not server_layers_by_name: # Raise ServiceException here. raise ServiceException(None, "Database type is not supported.") for layer_name, server_layer in server_layers_by_name.iteritems(): wms_layer = capabilities_wms.Layer( # 7.1.4.6.3 - Even for vector maps we always get data from # the server, even if it's just a transparent tile. By # jeffdonner's reading, this means that even the vector # layers are 'opaque'. opaque=1, cascaded=0, fixedHeight=None, fixedWidth=None, # We can subset. noSubsets=0, queryable=0, Name=layer_name, Title=capabilities_wms.Title(server_layer.label)) proj = server_layer.projection for epsg_name in proj.EPSG_NAMES: wms_layer.add_SRS(capabilities_wms.SRS(epsg_name)) wms_layer.set_LatLonBoundingBox( capabilities_wms.LatLonBoundingBox( minx=-proj.MAX_LONGITUDE, miny=-proj.MAX_LATITUDE, maxx=proj.MAX_LONGITUDE, maxy=proj.MAX_LATITUDE)) map_limits = server_layer.projection.AdvertizedLogOuterBounds() wms_layer.set_BoundingBox([ capabilities_wms.BoundingBox( SRS=epsg_name, minx=map_limits.x0, miny=map_limits.y0, maxx=map_limits.x1, maxy=map_limits.y1) for epsg_name in proj.EPSG_NAMES]) outer_layer.add_Layer(wms_layer) self.capabilities_xml.get_Capability().set_Layer(outer_layer)
def GenerateOutputCommon(self): """Produces the WMS bitmap image. Used by both versions. Returns: The image composed of tiles. """ logger.debug("Generating the bitmap image") image_format = utils.GetValue(self.parameters, "format") image_spec = image_specs.GetImageSpec(image_format) # TRANSPARENT parameter from GIS client's. # It can take "TRUE"/"FALSE" values. # Default value is "FALSE" as per spec. # GIS clients either send TRANSPARENT(=TRUE) attribute or don't send it # at all, if it's not required. # If this parameter is absent, GetValue() method returns None. # We default it to "FALSE" if this parameter is not available in the # GIS client requests. is_transparent = utils.GetValue(self.parameters, "transparent") if not is_transparent: is_transparent = "FALSE" # BGCOLOR parameter is a string that specifies the colour to be # used as the background (non-data) pixels of the map. # The format is 0xRRGGBB.The default value is 0xFFFFFF bgcolor = utils.GetValue(self.parameters, "bgcolor") if bgcolor: # Convert HEX string to python tuple. # Ignore the 0x at the beginning of the hex string. # Otherwise str.decode("hex") will throw # "TypeError: Non-hexadecimal digit found" error. if bgcolor[:2] == "0x": bgcolor = bgcolor[2:] bgcolor = tuple(ord(c) for c in bgcolor.decode("hex")) else: bgcolor = tiles.ALL_WHITE_PIXELS # Add the user requested image format, transparency # and bgcolor to the layer python object. self.requested_layer_obj.image_format = image_format self.requested_layer_obj.is_transparent = is_transparent self.requested_layer_obj.bgcolor = bgcolor im_user = tiles.ProduceImage( self.requested_layer_obj, # There's a weird border artifact that MapServer shows when we # ask for more than 360 laterally, and which natively we show # fine. It's probably not us, but there's room for doubt. If # it was our calculations, it's likely the error would be most # extreme for a large final image, and few tiles. self.user_log_rect, self.user_width, self.user_height) buf = StringIO.StringIO() im_user.save(buf, image_spec.pil_format, **im_user.info) logger.debug("Image content type is :%s", image_spec.content_type) headers = [("Content-Type", image_spec.content_type)] logger.debug("Done generating the bitmap image") return headers, buf.getvalue()
def GenerateOutputCommon(self): """Produces the WMS bitmap image. Used by both versions. Returns: The image composed of tiles. """ logger.debug("Generating the bitmap image") image_format = utils.GetValue(self.parameters, "format") image_spec = image_specs.GetImageSpec(image_format) logger.info( "WMS Request image_format: %s image_spec: %s will be applied to all layers.", image_format, image_spec) # TRANSPARENT parameter from GIS client's. # It can take "TRUE"/"FALSE" values. # Default value is "FALSE" as per spec. # GIS clients either send TRANSPARENT(=TRUE) attribute or don't send it # at all, if it's not required. # If this parameter is absent, GetValue() method returns None. # We default it to "FALSE" if this parameter is not available in the # GIS client requests. is_transparent = utils.GetValue(self.parameters, "transparent") if not is_transparent: is_transparent = "FALSE" # BGCOLOR parameter is a string that specifies the colour to be # used as the background (non-data) pixels of the map. # The format is 0xRRGGBB.The default value is 0xFFFFFF bgcolor = utils.GetValue(self.parameters, "bgcolor") if bgcolor: # Convert HEX string to python tuple. # Ignore the 0x at the beginning of the hex string. # Otherwise str.decode("hex") will throw # "TypeError: Non-hexadecimal digit found" error. if bgcolor[:2] == "0x": bgcolor = bgcolor[2:] bgcolor = tuple(ord(c) for c in bgcolor.decode("hex")) else: bgcolor = tiles.ALL_WHITE_PIXELS layer_names = self._ExtractLayerNames( utils.GetValue(self.parameters, 'layers')) logger.info("Loop through layers: %s ", (str(layer_names))) composite_image = None for layer_name in layer_names: # Add the user requested image format, transparency # and bgcolor to the layer python object. layer_obj = self._ExtractLayerObj(layer_name) logger.debug("Processing a layer: %s \n%s ", layer_name, pformat(layer_obj)) layer_obj.image_format = image_format layer_obj.is_transparent = is_transparent layer_obj.bgcolor = bgcolor # We'll assume that all WMS layers will have the same image_spec and projection # for im_user = tiles.ProduceImage( layer_obj, # There's a weird border artifact that MapServer shows when we # ask for more than 360 laterally, and which natively we show # fine. It's probably not us, but there's room for doubt. If # it was our calculations, it's likely the error would be most # extreme for a large final image, and few tiles. self.user_log_rect, self.user_width, self.user_height) im_user = im_user.convert("RGBA") if composite_image is None: composite_image = im_user else: logger.debug("Adding layer %s to composite image...", layer_name) composite_image.paste(im_user, (0, 0), im_user) buf = StringIO.StringIO() output_format = image_spec.pil_format composite_image.save(buf, image_spec.pil_format, **im_user.info) headers = [("Content-Type", image_spec.content_type)] return headers, buf.getvalue()