class BackendComponent(Component): file_storages = ExtensionPoint(FileStorageInterface) connected_storages = ExtensionPoint(ConnectedStorageInterface) packages = ExtensionPoint(PackageInterface) @property def storages(self): """ Helper for all storages. """ return itertools.chain(self.file_storages, self.connected_storages) def get_file_storage_component(self, storage_type): storage_type = storage_type.upper() result_component = None for storage_component in self.file_storages: if storage_component.name.upper() == storage_type: if result_component is not None: raise Exception("Ambigouus storage component") result_component = storage_component return result_component def get_connected_storage_component(self, storage_type): storage_type = storage_type.upper() result_component = None for storage_component in self.connected_storages: if storage_component.name.upper() == storage_type: if result_component is not None: raise Exception("Ambigouus storage component") result_component = storage_component return result_component def get_storage_component(self, storage_type): file_storage = self.get_file_storage_component(storage_type) connected_storage = self.get_connected_storage_component(storage_type) if file_storage is not None and connected_storage is not None: raise Exception("Ambigouus storage component") return file_storage or connected_storage def get_package_component(self, format): format = format.upper() result_component = None for package_component in self.packages: if package_component.name.upper() == format: if result_component is not None: raise Exception("Ambigouus package component") result_component = package_component return result_component
class PDPComponent(Component): pdps = ExtensionPoint(PolicyDecisionPointInterface) def get_pdp(self, pdp_type): for pdp in self.pdps: if pdp.pdp_type == pdp_type: return pdp return None
class MetadataComponent(Component): metadata_readers = ExtensionPoint(MetadataReaderInterface) metadata_writers = ExtensionPoint(MetadataWriterInterface) def get_reader_by_test(self, obj): for reader in self.metadata_readers: if reader.test(obj): return reader return None def get_reader_by_format(self, format): for reader in self.metadata_readers: if format in reader.formats: return reader return None def get_writer_by_format(self, format): for writer in self.metadata_writers: if format in writer.formats: return writer return None
class WCS20GetCoverageHandler(WCSGetCoverageHandlerBase, Component): implements(ServiceHandlerInterface) implements(GetServiceHandlerInterface) implements(PostServiceHandlerInterface) encoding_extensions = ExtensionPoint(EncodingExtensionInterface) versions = ("2.0.0", "2.0.1") def get_decoder(self, request): if request.method == "GET": return WCS20GetCoverageKVPDecoder(request.GET) elif request.method == "POST": return WCS20GetCoverageXMLDecoder(request.body) def get_params(self, coverage, decoder, request): subsets = Subsets(decoder.subsets, crs=decoder.subsettingcrs) encoding_params = None for encoding_extension in self.encoding_extensions: if encoding_extension.supports(decoder.format, {}): encoding_params = encoding_extension.get_encoding_params( request ) scalefactor = decoder.scalefactor scales = list( chain(decoder.scaleaxes, decoder.scalesize, decoder.scaleextent) ) # check scales validity: ScaleFactor and any other scale if scalefactor and scales: raise InvalidRequestException( "ScaleFactor and any other scale operation are mutually " "exclusive.", locator="scalefactor" ) # check scales validity: Axis uniqueness axes = set() for scale in scales: if scale.axis in axes: raise InvalidRequestException( "Axis '%s' is scaled multiple times." % scale.axis, locator=scale.axis ) axes.add(scale.axis) return WCS20CoverageRenderParams( coverage, subsets, decoder.rangesubset, decoder.format, decoder.outputcrs, decoder.mediatype, decoder.interpolation, scalefactor, scales, encoding_params or {}, request )
class WPS10GetCapabilitiesHandler(Component): """ WPS 1.0 GetCapabilities service handler. """ implements(ServiceHandlerInterface) implements(GetServiceHandlerInterface) implements(PostServiceHandlerInterface) implements(VersionNegotiationInterface) service = "WPS" versions = ("1.0.0", ) request = "GetCapabilities" processes = ExtensionPoint(ProcessInterface) def handle(self, request): """ Handle HTTP request. """ encoder = WPS10CapabilitiesXMLEncoder() return encoder.serialize(encoder.encode_capabilities(self.processes))
class WPS10DescribeProcessHandler(Component): """ WPS 1.0 DescribeProcess service handler. """ implements(ServiceHandlerInterface) implements(GetServiceHandlerInterface) implements(PostServiceHandlerInterface) service = "WPS" versions = ("1.0.0",) request = "DescribeProcess" processes = ExtensionPoint(ProcessInterface) @staticmethod def get_decoder(request): """ Get the WPS request decoder. """ if request.method == "GET": return WPS10DescribeProcessKVPDecoder(request.GET) else: return WPS10DescribeProcessXMLDecoder(request.body) def handle(self, request): """ Handle HTTP request. """ decoder = self.get_decoder(request) identifiers = set(decoder.identifiers) used_processes = [] for process in self.processes: process_identifier = ( getattr(process, 'identifier', None) or type(process).__name__ ) if process_identifier in identifiers: identifiers.remove(process_identifier) used_processes.append(process) for identifier in identifiers: raise NoSuchProcessError(identifier) encoder = WPS10ProcessDescriptionsXMLEncoder() return encoder.serialize( encoder.encode_process_descriptions(used_processes) )
class MapServerWMSBaseComponent(Component): """ Base class for various WMS render components using MapServer. """ connectors = ExtensionPoint(ConnectorInterface) layer_factories = ExtensionPoint(LayerFactoryInterface) style_applicators = ExtensionPoint(StyleApplicatorInterface) def render(self, layer_groups, request_values, **options): map_ = ms.Map() map_.setMetaData("ows_enable_request", "*") map_.setProjection("EPSG:4326") map_.imagecolor.setRGB(0, 0, 0) # set supported CRSs decoder = CRSsConfigReader(get_eoxserver_config()) crss_string = " ".join( map(lambda crs: "EPSG:%d" % crs, decoder.supported_crss_wms) ) map_.setMetaData("ows_srs", crss_string) map_.setMetaData("wms_srs", crss_string) self.check_parameters(map_, request_values) session = self.setup_map(layer_groups, map_, options) with session: request = ms.create_request(request_values) raw_result = map_.dispatch(request) result = result_set_from_raw_data(raw_result) return result, get_content_type(result) def check_parameters(self, map_, request_values): for key, value in request_values: if key.lower() == "format": if not map_.getOutputFormatByName(value): raise InvalidFormat(value) break else: raise RenderException("Missing 'format' parameter") @property def suffixes(self): return list( chain(*[factory.suffixes for factory in self.layer_factories]) ) def get_connector(self, data_items): for connector in self.connectors: if connector.supports(data_items): return connector return None def get_layer_factory(self, suffix): result = None for factory in self.layer_factories: if suffix in factory.suffixes: if result: pass # TODO #raise Exception("Found") result = factory return result return result def setup_map(self, layer_selection, map_, options): group_layers = SortedDict() session = ConnectorSession(options) # set up group layers before any "real" layers for collections, _, name, suffix in tuple(layer_selection.walk()): if not collections: continue # get a factory for the given suffix factory = self.get_layer_factory(suffix) if not factory: # raise or pass? continue # get the groups name, which is the name of the collection + the # suffix group_name = collections[-1].identifier + (suffix or "") # generate a group layer group_layer = factory.generate_group(group_name) group_layers[group_name] = group_layer # set up the actual layers for each coverage for collections, coverage, name, suffix in layer_selection.walk(): # get a factory for the given coverage and suffix factory = self.get_layer_factory(suffix) group_layer = None group_name = None if collections: group_name = collections[-1].identifier + (suffix or "") group_layer = group_layers.get(group_name) if not coverage: # add an empty layer to not produce errors out of bounds. if name: tmp_layer = ms.layerObj() tmp_layer.name = (name + suffix) if suffix else name layers_and_data_items = ((tmp_layer, ()),) else: layers_and_data_items = () elif not factory: tmp_layer = ms.layerObj() tmp_layer.name = name layers_and_data_items = ((tmp_layer, ()),) else: data_items = coverage.data_items.all() coverage.cached_data_items = data_items layers_and_data_items = tuple(factory.generate( coverage, group_layer, suffix, options )) for layer, data_items in layers_and_data_items: connector = self.get_connector(data_items) if group_name: layer.setMetaData("wms_layer_group", "/" + group_name) session.add(connector, coverage, data_items, layer) coverage_layers = [layer for _, layer, _ in session.coverage_layers] for layer in chain(group_layers.values(), coverage_layers): old_layer = map_.getLayerByName(layer.name) if old_layer: # remove the old layer and reinsert the new one, to # raise the layer to the top. # TODO: find a more efficient way to do this map_.removeLayer(old_layer.index) map_.insertLayer(layer) # apply any styles # TODO: move this to map/legendgraphic renderer only? for coverage, layer, data_items in session.coverage_layers: for applicator in self.style_applicators: applicator.apply(coverage, data_items, layer) return session def get_empty_layers(self, name): layer = ms.layerObj() layer.name = name layer.setMetaData("wms_enable_request", "getmap") return (layer,)
class GDALDatasetMetadataReader(Component): implements(MetadataReaderInterface) additional_readers = ExtensionPoint(GDALDatasetMetadataReaderInterface) def test(self, obj): return open_gdal(obj) is not None def get_format_name(self, obj): ds = open_gdal(obj) if not ds: return None driver = ds.GetDriver() return "GDAL/" + driver.ShortName def read(self, obj): ds = open_gdal(obj) if ds is None: raise Exception("Could not parse from obj '%s'." % repr(obj)) driver = ds.GetDriver() size = (ds.RasterXSize, ds.RasterYSize) values = {"size": size} # --= rectified datasets =-- # NOTE: If the projection is a non-zero string then # the geocoding is given by the Geo-Trasnformation # matrix - not matter what are the values. if ds.GetProjection(): values["coverage_type"] = "RectifiedDataset" values["projection"] = (ds.GetProjection(), "WKT") # get coordinates of all four image corners gt = ds.GetGeoTransform() def gtrans(x, y): return gt[0] + x * gt[1] + y * gt[2], gt[ 3] + x * gt[4] + y * gt[5] vpix = [(0, 0), (0, size[1]), (size[0], 0), (size[0], size[1])] vx, vy = zip(*(gtrans(x, y) for x, y in vpix)) # find the extent values["extent"] = (min(vx), min(vy), max(vx), max(vy)) # --= tie-point encoded referenceable datasets =-- # NOTE: If the GCP projection is a non-zero string and # there are GCPs we are dealing with a tie-point geocoded # referenceable dataset. The extent is given by the image # footprint. The fooprint must not be wrapped arround # the date-line! elif ds.GetGCPProjection() and ds.GetGCPCount() > 0: values["coverage_type"] = "ReferenceableDataset" projection = ds.GetGCPProjection() values["projection"] = (projection, "WKT") # parse the spatial reference to get the EPSG code sr = osr.SpatialReference(projection, "WKT") # NOTE: GeosGeometry can't handle non-EPSG geometry projections. if sr.GetAuthorityName(None) == "EPSG": srid = int(sr.GetAuthorityCode(None)) # get the footprint rt_prm = rt.suggest_transformer(ds) fp_wkt = rt.get_footprint_wkt(ds, **rt_prm) footprint = GEOSGeometry(fp_wkt, srid) if isinstance(footprint, Polygon): footprint = MultiPolygon(footprint) elif not isinstance(footprint, MultiPolygon): raise TypeError("Got invalid geometry %s" % type(footprint).__name__) values["footprint"] = footprint values["extent"] = footprint.extent # --= dataset with no geocoding =-- # TODO: Handling of other types of GDAL geocoding (e.g, RPC). else: pass reader = self._find_additional_reader(ds) if reader: additional_values = reader.read_ds(ds) for key, value in additional_values.items(): values.setdefault(key, value) driver_metadata = driver.GetMetadata() frmt = driver_metadata.get("DMD_MIMETYPE") if frmt: values["format"] = frmt return values def _find_additional_reader(self, ds): for reader in self.additional_readers: if reader.test_ds(ds): return reader return None
class WCSGetCapabilitiesHandlerBase(object): """ Base for Coverage description handlers. """ service = "WCS" request = "GetCapabilities" index = 0 renderers = ExtensionPoint(WCSCapabilitiesRendererInterface) def get_decoder(self, request): """ Interface method to get the correct decoder for this request. """ def lookup_coverages(self, decoder): """ Default implementation of the coverage lookup. Simply returns all coverages in no specific order. """ return models.Coverage.objects.filter(visible=True) \ .order_by("identifier") def get_params(self, coverages, decoder): """ Default method to return a render params object from the given coverages/decoder. """ return WCSCapabilitiesRenderParams( coverages, getattr(decoder, "version", None), getattr(decoder, "sections", None), getattr(decoder, "acceptlanguages", None), getattr(decoder, "acceptformats", None), getattr(decoder, "updatesequence", None), ) def get_renderer(self, params): """ Default implementation for a renderer retrieval. """ for renderer in self.renderers: if renderer.supports(params): return renderer raise OperationNotSupportedException( "No Capabilities renderer found for the given parameters.", self.request) def to_http_response(self, result_set): """ Default result to response conversion method. """ return to_http_response(result_set) def handle(self, request): """ Default handler method. """ # parse the parameters decoder = self.get_decoder(request) # get the coverages coverages = self.lookup_coverages(decoder) # create the render params params = self.get_params(coverages, decoder) params.http_request = request # get the renderer renderer = self.get_renderer(params) # dispatch the renderer and return the response result_set = renderer.render(params) return self.to_http_response(result_set)
class WCSGetCoverageHandlerBase(object): """ Base for get coverage handlers. """ service = "WCS" request = "GetCoverage" index = 10 renderers = ExtensionPoint(WCSCoverageRendererInterface) def get_decoder(self, request): """ Interface method to get the correct decoder for this request. """ def lookup_coverage(self, decoder): """ Default implementation of the coverage lookup. Returns the coverage model for the given request decoder or raises an exception if it is not found. """ coverage_id = decoder.coverage_id try: coverage = models.Coverage.objects.get(identifier=coverage_id) except models.Coverage.DoesNotExist: raise NoSuchCoverageException((coverage_id, )) return coverage def get_params(self, coverages, decoder, request): """ Interface method to return a render params object from the given coverages/decoder. """ def get_renderer(self, params): """ Default implementation for a renderer retrieval. """ for renderer in self.renderers: if renderer.supports(params): return renderer raise OperationNotSupportedException( "No renderer found for coverage '%s'." % params.coverage, self.request) def to_http_response(self, result_set): """ Default result to response conversion method. """ return to_http_response(result_set) def handle(self, request): """ Default handling method implementation. """ # parse the request decoder = self.get_decoder(request) # get the coverage model coverage = self.lookup_coverage(decoder) # create the render params params = self.get_params(coverage, decoder, request) # get the renderer renderer = self.get_renderer(params) # render the coverage and return the response result_set = renderer.render(params) return self.to_http_response(result_set)
class WCSDescribeCoverageHandlerBase(object): """ Base for Coverage description handlers. """ service = "WCS" request = "DescribeCoverage" index = 1 renderers = ExtensionPoint(WCSCoverageDescriptionRendererInterface) def get_decoder(self, request): """ Interface method to get the correct decoder for this request. """ def lookup_coverages(self, decoder): """ Default implementation of the coverage lookup. Returns a sorted list of coverage models according to the decoders `coverage_ids` attribute. Raises a `NoSuchCoverageException` if any of the given IDs was not found in the database. """ ids = decoder.coverage_ids coverages = sorted( models.Coverage.objects.filter(identifier__in=ids), key=(lambda coverage: ids.index(coverage.identifier))) # check correct number if len(coverages) < len(ids): available_ids = set( [coverage.identifier for coverage in coverages]) raise NoSuchCoverageException(set(ids) - available_ids) return coverages def get_params(self, coverages, decoder): """ Interface method to return a render params object from the given coverages/decoder. """ def get_renderer(self, params): """ Default implementation for a renderer retrieval. """ for renderer in self.renderers: if renderer.supports(params): return renderer raise OperationNotSupportedException( "No suitable coverage description renderer found.", self.request) def to_http_response(self, result_set): """ Default result to response conversion method. """ return to_http_response(result_set) def handle(self, request): """ Default request handling method implementation. """ # parse the parameters decoder = self.get_decoder(request) # lookup the coverages coverages = self.lookup_coverages(decoder) # create the render parameters params = self.get_params(coverages, decoder) # find the correct renderer renderer = self.get_renderer(params) # render and return the response result_set = renderer.render(params) return self.to_http_response(result_set)
class RectifiedCoverageMapServerRenderer(BaseRenderer): """ A coverage renderer for rectified coverages. Uses mapserver to process the request. """ implements(WCSCoverageRendererInterface) # ReferenceableDatasets are not handled in WCS >= 2.0 versions_full = (Version(1, 1), Version(1, 0)) versions_partly = (Version(2, 0), ) versions = versions_full + versions_partly handles_full = (models.RectifiedDataset, models.RectifiedStitchedMosaic, models.ReferenceableDataset) handles_partly = (models.RectifiedDataset, models.RectifiedStitchedMosaic) handles = handles_full + handles_partly connectors = ExtensionPoint(ConnectorInterface) layer_factories = ExtensionPoint(LayerFactoryInterface) def supports(self, params): return ((params.version in self.versions_full and issubclass(params.coverage.real_type, self.handles_full)) or (params.version in self.versions_partly and issubclass( params.coverage.real_type, self.handles_partly))) def render(self, params): # get coverage related stuff coverage = params.coverage # ReferenceableDataset are not supported in WCS < 2.0 if issubclass(coverage.real_type, models.ReferenceableDataset): raise NoSuchCoverageException((coverage.identifier, )) data_items = self.data_items_for_coverage(coverage) range_type = coverage.range_type bands = list(range_type) subsets = params.subsets if subsets: subsets.srid # this automatically checks the validity # create and configure map object map_ = self.create_map() # configure outputformat native_format = self.get_native_format(coverage, data_items) if get_format_by_mime(native_format) is None: native_format = "image/tiff" frmt = params.format or native_format if frmt is None: raise RenderException("Format could not be determined", "format") mime_type, frmt = split_format(frmt) imagemode = ms.gdalconst_to_imagemode(bands[0].data_type) time_stamp = datetime.now().strftime("%Y%m%d%H%M%S") basename = "%s_%s" % (coverage.identifier, time_stamp) of = create_outputformat(mime_type, frmt, imagemode, basename, getattr(params, "encoding_params", {})) map_.appendOutputFormat(of) map_.setOutputFormat(of) # TODO: use layer factory here layer = self.layer_for_coverage(coverage, native_format, params.version) map_.insertLayer(layer) for connector in self.connectors: if connector.supports(data_items): break else: raise OperationNotSupportedException( "Could not find applicable layer connector.", "coverage") try: connector.connect(coverage, data_items, layer, {}) # create request object and dispatch it against the map request = ms.create_request( self.translate_params(params, range_type)) request.setParameter("format", mime_type) raw_result = ms.dispatch(map_, request) finally: # perform any required layer related cleanup connector.disconnect(coverage, data_items, layer, {}) result_set = result_set_from_raw_data(raw_result) if params.version == Version(2, 0): if getattr(params, "mediatype", None) in ("multipart/mixed", "multipart/related"): encoder = WCS20EOXMLEncoder() is_mosaic = issubclass(coverage.real_type, models.RectifiedStitchedMosaic) if not is_mosaic: tree = encoder.alter_rectified_dataset( coverage, getattr(params, "http_request", None), etree.parse(result_set[0].data_file).getroot(), subsets.bounding_polygon(coverage) if subsets else None) else: tree = encoder.alter_rectified_stitched_mosaic( coverage.cast(), getattr(params, "http_request", None), etree.parse(result_set[0].data_file).getroot(), subsets.bounding_polygon(coverage) if subsets else None) result_set[0] = ResultBuffer(encoder.serialize(tree), encoder.content_type) # "default" response return result_set def translate_params(self, params, range_type): """ "Translate" parameters to be understandable by mapserver. """ if params.version.startswith("2.0"): for key, value in params: if key == "interpolation": interpolation = INTERPOLATION_TRANS.get(value) if not interpolation: raise InterpolationMethodNotSupportedException( "Interpolation method '%s' is not supported." % value) yield key, value else: yield key, value rangesubset = params.rangesubset if rangesubset: yield "rangesubset", ",".join( map(str, rangesubset.get_band_indices(range_type, 1))) # TODO: this only works in newer MapServer implementations # (since 6.4?). SCALE_AVAILABLE = ms.msGetVersionInt() > 60401 scalefactor = params.scalefactor if scalefactor is not None: if SCALE_AVAILABLE: yield "scalefactor", str(scalefactor) else: raise RenderException( "'ScaleFactor' is not supported by MapServer in the " "current version.", "scalefactor") for scale in params.scales: scaleaxes = [] if isinstance(scale, ScaleSize): yield "size", "%s(%d)" % (scale.axis, scale.size) elif isinstance(scale, ScaleExtent): yield "size", "%s(%d)" % (scale.axis, scale.high - scale.low) elif isinstance(scale, ScaleAxis): if SCALE_AVAILABLE: scaleaxes.append(scale) else: raise RenderException( "'ScaleAxes' is not supported by MapServer in the " "current version.", "scaleaxes") if scaleaxes: yield "scaleaxes", ",".join("%s(%f)" % (scale.axis, scale.scale) for scale in scaleaxes) if params.outputcrs is not None: srid = crss.parseEPSGCode( params.outputcrs, (crss.fromURL, crss.fromURN, crss.fromShortCode)) if srid is None: raise InvalidOutputCrsException( "Failed to extract an EPSG code from the OutputCRS URI " "'%s'." % params.outputcrs) yield "outputcrs", params.outputcrs else: for key, value in params: yield key, value
class OpenSearch11SearchHandler(Component): search_extensions = ExtensionPoint(SearchExtensionInterface) result_formats = ExtensionPoint(ResultFormatInterface) def handle(self, request, collection_id=None, format_name=None): if request.method == "GET": request_parameters = request.GET elif request.method == "POST": request_parameters = request.POST else: raise Exception("Invalid request method '%s'." % request.method) decoder = OpenSearch11BaseDecoder(request_parameters) if collection_id: qs = models.Collection.objects.get( identifier=collection_id ).eo_objects.all() else: qs = models.Collection.objects.all() if decoder.search_terms: # TODO: search descriptions, summary etc once available qs = qs.filter(identifier__icontains=decoder.search_terms) namespaces = NameSpaceMap() all_parameters = {} for search_extension in self.search_extensions: # get all search extension related parameters and translate the name # to the actual parameter name params = dict( (parameter["type"], request_parameters[parameter["name"]]) for parameter in search_extension.get_schema() if parameter["name"] in request_parameters ) qs = search_extension.filter(qs, params) namespaces.add(search_extension.namespace) all_parameters[search_extension.namespace.prefix] = params total_count = len(qs) if decoder.start_index and not decoder.count: qs = qs[decoder.start_index:] elif decoder.start_index and decoder.count: qs = qs[decoder.start_index:decoder.start_index+decoder.count] elif decoder.count: qs = qs[:decoder.count] elif decoder.count == 0: if collection_id: qs = models.Collection.objects.none() else: qs = models.EOObject.objects.none() try: result_format = next( result_format for result_format in self.result_formats if result_format.name == format_name ) except StopIteration: raise Http404("No such result format '%s'." % format_name) default_page_size = 100 # TODO: make this configurable search_context = SearchContext( total_count, decoder.start_index, decoder.count or default_page_size, len(qs), all_parameters, namespaces ) return ( result_format.encode(request, collection_id, qs, search_context), result_format.mimetype )
class WCS20GetEOCoverageSetHandler(Component): implements(ServiceHandlerInterface) implements(GetServiceHandlerInterface) implements(PostServiceHandlerInterface) coverage_renderers = ExtensionPoint(WCSCoverageRendererInterface) package_writers = ExtensionPoint(PackageWriterInterface) service = "WCS" versions = ("2.0.0", "2.0.1") methods = ['GET', 'POST'] request = "GetEOCoverageSet" index = 21 def get_decoder(self, request): if request.method == "GET": return WCS20GetEOCoverageSetKVPDecoder(request.GET) elif request.method == "POST": return WCS20GetEOCoverageSetXMLDecoder(request.body) def get_params(self, coverage, decoder, request): return WCS20CoverageRenderParams(coverage, Subsets(decoder.subsets), http_request=request) def get_renderer(self, params): for renderer in self.coverage_renderers: if renderer.supports(params): return renderer raise InvalidRequestException( "Could not find renderer for coverage '%s'.") def get_pacakge_writer(self, format, params): for writer in self.package_writers: if writer.supports(format, params): return writer raise InvalidRequestException("Format '%s' is not supported." % format, locator="format") @property def constraints(self): reader = WCSEOConfigReader(get_eoxserver_config()) return {"CountDefault": reader.paging_count_default} def handle(self, request): decoder = self.get_decoder(request) eo_ids = decoder.eo_ids format, format_params = decoder.format writer = self.get_pacakge_writer(format, format_params) containment = decoder.containment count_default = self.constraints["CountDefault"] count = decoder.count if count_default is not None: count = min(count, count_default) try: subsets = Subsets(decoder.subsets, crs="http://www.opengis.net/def/crs/EPSG/0/4326", allowed_types=Trim) except ValueError as e: raise InvalidSubsettingException(str(e)) if len(eo_ids) == 0: raise # fetch a list of all requested EOObjects available_ids = models.EOObject.objects.filter( identifier__in=eo_ids).values_list("identifier", flat=True) # match the requested EOIDs against the available ones. If any are # requested, that are not available, raise and exit. failed = [eo_id for eo_id in eo_ids if eo_id not in available_ids] if failed: raise NoSuchDatasetSeriesOrCoverageException(failed) collections_qs = subsets.filter( models.Collection.objects.filter(identifier__in=eo_ids), containment="overlaps") # create a set of all indirectly referenced containers by iterating # recursively. The containment is set to "overlaps", to also include # collections that might have been excluded with "contains" but would # have matching coverages inserted. def recursive_lookup(super_collection, collection_set): sub_collections = models.Collection.objects.filter( collections__in=[super_collection.pk]).exclude( pk__in=map(lambda c: c.pk, collection_set)) sub_collections = subsets.filter(sub_collections, "overlaps") # Add all to the set collection_set |= set(sub_collections) for sub_collection in sub_collections: recursive_lookup(sub_collection, collection_set) collection_set = set(collections_qs) for collection in set(collection_set): recursive_lookup(collection, collection_set) collection_pks = map(lambda c: c.pk, collection_set) # Get all either directly referenced coverages or coverages that are # within referenced containers. Full subsetting is applied here. coverages_qs = models.Coverage.objects.filter( Q(identifier__in=eo_ids) | Q(collections__in=collection_pks)) coverages_qs = subsets.filter(coverages_qs, containment=containment) # save a reference before limits are applied to obtain the full number # of matched coverages. coverages_no_limit_qs = coverages_qs # compute how many (if any) coverages can be retrieved. This depends on # the "count" parameter and default setting. Also, if we already # exceeded the count, limit the number of dataset series aswell """ if inc_dss_section: num_collections = len(collection_set) else: num_collections = 0 if num_collections < count and inc_cov_section: coverages_qs = coverages_qs.order_by("identifier")[:count - num_collections] elif num_collections == count or not inc_cov_section: coverages_qs = [] else: coverages_qs = [] collection_set = sorted(collection_set, key=lambda c: c.identifier)[:count] """ # get a number of coverages that *would* have been included, but are not # because of the count parameter # count_all_coverages = coverages_no_limit_qs.count() # TODO: if containment is "within" we need to check all collections # again if containment == "within": collection_set = filter(lambda c: subsets.matches(c), collection_set) coverages = [] dataset_series = [] # finally iterate over everything that has been retrieved and get # a list of dataset series and coverages to be encoded into the response for eo_object in chain(coverages_qs, collection_set): if issubclass(eo_object.real_type, models.Coverage): coverages.append(eo_object.cast()) fd, pkg_filename = tempfile.mkstemp() tmp = os.fdopen(fd) tmp.close() package = writer.create_package(pkg_filename, format, format_params) for coverage in coverages: params = self.get_params(coverage, decoder, request) renderer = self.get_renderer(params) result_set = renderer.render(params) all_filenames = set() for result_item in result_set: if not result_item.filename: ext = mimetypes.guess_extension(result_item.content_type) filename = coverage.identifier + ext else: filename = result_item.filename if filename in all_filenames: continue # TODO: create new filename all_filenames.add(filename) location = "%s/%s" % (coverage.identifier, filename) writer.add_to_package(package, result_item.data_file, result_item.size, location) mime_type = writer.get_mime_type(package, format, format_params) ext = writer.get_file_extension(package, format, format_params) writer.cleanup(package) response = StreamingHttpResponse(tempfile_iterator(pkg_filename), mime_type) response["Content-Disposition"] = 'inline; filename="ows%s"' % ext response["Content-Length"] = str(os.path.getsize(pkg_filename)) return response
class WPS10ExcecuteHandler(Component): """ WPS 1.0 Execute service handler. """ implements(ServiceHandlerInterface) implements(GetServiceHandlerInterface) implements(PostServiceHandlerInterface) service = "WPS" versions = ("1.0.0",) request = "Execute" processes = ExtensionPoint(ProcessInterface) async_backends = ExtensionPoint(AsyncBackendInterface) @staticmethod def get_decoder(request): """ Get request decoder matching the request format. """ if request.method == "GET": return WPS10ExecuteKVPDecoder( parse_query_string(request.META['QUERY_STRING']) ) elif request.method == "POST": # support for multipart items if request.META["CONTENT_TYPE"].startswith("multipart/"): _, data = next(mp.iterate(request.body)) return WPS10ExecuteXMLDecoder(data) return WPS10ExecuteXMLDecoder(request.body) def get_process(self, identifier): """ Get process component matched by the identifier. """ for process in self.processes: process_identifier = ( getattr(process, 'identifier', None) or type(process).__name__ ) if process_identifier == identifier: return process raise NoSuchProcessError(identifier) def get_async_backend(self): """ Get available asynchronous back-end matched by the service version. """ version_set = set(self.versions) for backend in self.async_backends: if set(backend.supported_versions) & version_set: return backend def handle(self, request): # pylint: disable=redefined-variable-type, too-many-locals """ Request handler. """ logger = getLogger(__name__) # decode request decoder = self.get_decoder(request) # parse named requests parts used in case of cid references extra_parts = parse_named_parts(request) # get the process and convert input/output definitions to a common format process = self.get_process(decoder.identifier) logger.debug("Execute process %s", decoder.identifier) input_defs = parse_params(process.inputs) output_defs = parse_params(process.outputs) # get the unparsed (raw) inputs and the requested response parameters raw_inputs = check_invalid_inputs(decoder.inputs, input_defs) resp_form = check_invalid_outputs(decoder.response_form, output_defs) # resolve the special request input parameters raw_inputs = resolve_request_parameters(raw_inputs, input_defs, request) if resp_form.raw: encoder = WPS10ExecuteResponseRawEncoder(resp_form) else: encoder = WPS10ExecuteResponseXMLEncoder( process, resp_form, raw_inputs ) if not resp_form.raw and resp_form.store_response: # asynchronous execution async_backend = self.get_async_backend() if not async_backend: raise StorageNotSupported( "This service instance does not support asynchronous " "execution!" ) if not getattr(process, 'asynchronous', False): raise StorageNotSupported( "This process does not allow asynchronous execution!", ) if not resp_form.status: raise InvalidParameterValue( "The status update cannot be blocked for an asynchronous " "execute request!", "status" ) # pass the control over the processing to the asynchronous back-end job_id = async_backend.execute( process, raw_inputs, resp_form, extra_parts, request=request, ) # encode the StatusAccepted response encoder.status_location = async_backend.get_response_url(job_id) response = encoder.encode_accepted() else: # synchronous execution if not getattr(process, 'synchronous', True): raise InvalidParameterValue( "This process does not allow synchronous execution!", "storeExecuteResponse" ) if resp_form.status: raise InvalidParameterValue( "The status update cannot be provided for a synchronous " "execute request!", "status" ) # prepare inputs passed to the process execution subroutine inputs = {} inputs.update(decode_output_requests(resp_form, output_defs)) inputs.update(decode_raw_inputs( raw_inputs, input_defs, InMemoryURLResolver(extra_parts, logger) )) if not resp_form.raw: encoder.inputs = inputs # execute the process outputs = process.execute(**inputs) # pack the outputs packed_outputs = pack_outputs(outputs, resp_form, output_defs) # encode the StatusSucceeded response response = encoder.encode_response(packed_outputs) return encoder.serialize(response)
class ServiceComponent(Component): service_handlers = ExtensionPoint(ServiceHandlerInterface) exception_handlers = ExtensionPoint(ExceptionHandlerInterface) get_service_handlers = ExtensionPoint(GetServiceHandlerInterface) post_service_handlers = ExtensionPoint(PostServiceHandlerInterface) version_negotiation_handlers = ExtensionPoint(VersionNegotiationInterface) def __init__(self, *args, **kwargs): super(ServiceComponent, self).__init__(*args, **kwargs) def query_service_handler(self, request): """ Tries to find the correct service handler for a given request. The request ``method`` can either be "POST" (in which case the request body is parsed as XML) or "GET" (in which case the request is parsed as "KVP"). If necessary a version negotiation is conducted, following OWS guidelines. :param request: a :class:`Django HttpRequest <django.http.HttpRequest>` object :returns: the request handler component for the given request :raises ServiceNotSupportedException: if the service is not supported by any component :raises VersionNotSupportedException: if the specified version is not supported :raises OperationNotSupportedException: if the specified request operation is not supported """ decoder = get_decoder(request) if request.method == "GET": handlers = self.get_service_handlers elif request.method == "POST": handlers = self.post_service_handlers elif request.method == "OPTIONS": return OptionsRequestHandler() else: raise HTTPMethodNotAllowedError( "The %s HTTP method is not allowed!" % request.method, ALLOWED_HTTP_METHODS) #handlers = self.service_handlers version = decoder.version if version is None: accepted_versions = decoder.acceptversions handlers = filter_handlers(handlers, decoder.service, accepted_versions, decoder.request) return self.version_negotiation(handlers, accepted_versions) # check that the service is supported handlers = filter( partial(handler_supports_service, service=decoder.service), handlers) if not handlers: raise ServiceNotSupportedException(decoder.service) # check that the required version is enabled handlers_ = filter(lambda h: decoder.version in h.versions, handlers) if not handlers_: # old style version negotiation shall always return capabilities if decoder.request == "GETCAPABILITIES": handlers = [ sorted(filter( lambda h: decoder.request == h.request.upper(), handlers), key=lambda h: max(h.versions), reverse=True)[0] ] else: raise VersionNotSupportedException(decoder.service, decoder.version) else: handlers = handlers_ # check that the required operation is supported and sort by the highest # version supported in descending manner handlers = sorted(filter( lambda h: decoder.request == h.request.upper(), handlers), key=lambda h: max(h.versions), reverse=True) if not handlers: operation = decoder.request raise OperationNotSupportedException( "Operation '%s' is not supported." % operation, operation) # return the handler with the highest version logger.debug("Handling '%s' request for '%s' service version '%s'." % (handlers[0].request, handlers[0].service, handlers[0].versions[0])) return handlers[0] def query_service_handlers(self, service=None, versions=None, request=None, method=None): """ Query the service handler components, filtering optionally by ``service``, ``versions``, ``request`` or ``method``. """ method = method.upper() if method is not None else None if method == "GET": handlers = self.get_service_handlers elif method == "POST": handlers = self.post_service_handlers elif method is None: handlers = self.service_handlers else: return [] handlers = filter_handlers(handlers, service, versions, request) return sort_handlers(handlers) def query_exception_handler(self, request): try: decoder = get_decoder(request) handlers = self.exception_handlers handlers = sorted(filter( partial(handler_supports_service, service=decoder.service), self.exception_handlers), key=lambda h: max(h.versions), reverse=True) # try to get the correctly versioned exception handler if decoder.version: for handler in handlers: if decoder.version in handler.versions: return handler else: # return the exception handler with the highest version, # if one is available return handlers[0] except: # swallow any exception here, because we *really* need a handler # to correctly show the exception. pass # last resort fallback is a plain OWS exception handler return OWS20ExceptionHandler() def version_negotiation(self, handlers, accepted_versions=None): version_to_handler = {} for handler in handlers: for version in handler.versions: version_to_handler.setdefault(version, handler) available_versions = sorted(version_to_handler.keys(), reverse=True) if not available_versions: raise VersionNegotiationException() if not accepted_versions: return version_to_handler[available_versions[0]] combinations = itertools.product(accepted_versions, available_versions) for accepted_version, available_version in combinations: if accepted_version == available_version: return version_to_handler[available_version] raise VersionNegotiationException()
class RegistratorComponent(Component): registrators = ExtensionPoint(RegistratorInterface)
class BackendComponent(Component): """ This :class:`Component <eoxserver.core.component.Component>` provides extension points and helpers to easily retrive Package and Storage components by their type names. """ file_storages = ExtensionPoint(FileStorageInterface) connected_storages = ExtensionPoint(ConnectedStorageInterface) packages = ExtensionPoint(PackageInterface) @property def storages(self): """ Helper to retrieve components for all storage interfaces. """ return itertools.chain(self.file_storages, self.connected_storages) def get_file_storage_component(self, storage_type): """ Retrieve a component implementing the :class:`eoxserver.backends.interfaces.FileStorageInterface` with the desired ``storage_type``. :param storage_type: the desired storage type :returns: the desired storage component or ``None`` """ storage_type = storage_type.upper() result_component = None for storage_component in self.file_storages: if storage_component.name.upper() == storage_type: if result_component is not None: raise Exception("Ambigouus storage component") result_component = storage_component return result_component def get_connected_storage_component(self, storage_type): """ Retrieve a component implementing the :class:`eoxserver.backends.interfaces.ConnectedStorageInterface` with the desired ``storage_type``. :param storage_type: the desired storage type :returns: the desired storage component or ``None`` """ storage_type = storage_type.upper() result_component = None for storage_component in self.connected_storages: if storage_component.name.upper() == storage_type: if result_component is not None: raise Exception("Ambigouus storage component") result_component = storage_component return result_component def get_storage_component(self, storage_type): """ Retrieve a component implementing the :class:`eoxserver.backends.interfaces.FileStorageInterface` or :class:`eoxserver.backends.interfaces.ConnectedStorageInterface` with the desired ``storage_type``. :param storage_type: the desired storage type :returns: the desired storage component or ``None`` """ file_storage = self.get_file_storage_component(storage_type) connected_storage = self.get_connected_storage_component(storage_type) if file_storage is not None and connected_storage is not None: raise Exception("Ambigouus storage component") return file_storage or connected_storage def get_package_component(self, format): """ Retrieve a component implementing the :class:`eoxserver.backends.interfaces.PackageInterface` with the desired ``format``. :param format: the desired package format :returns: the desired package component or ``None`` """ format = format.upper() result_component = None for package_component in self.packages: if package_component.name.upper() == format: if result_component is not None: raise Exception("Ambigouus package component") result_component = package_component return result_component
class MapServerWMSCapabilitiesRenderer(Component): """ WMS Capabilities renderer implementation using MapServer. """ implements(WMSCapabilitiesRendererInterface) layer_factories = ExtensionPoint(LayerFactoryInterface) @property def suffixes(self): return list( chain(*[factory.suffixes for factory in self.layer_factories])) def render(self, collections, coverages, request_values, request): conf = CapabilitiesConfigReader(get_eoxserver_config()) suffixes = self.suffixes http_service_url = get_http_service_url(request) map_ = Map() map_.setMetaData( { "enable_request": "*", "onlineresource": http_service_url, "service_onlineresource": conf.onlineresource, "updateSequence": conf.update_sequence, "name": conf.name, "title": conf.title, "abstract": conf.abstract, "accessconstraints": conf.access_constraints, "addresstype": "postal", "address": conf.delivery_point, "stateorprovince": conf.administrative_area, "city": conf.city, "postcode": conf.postal_code, "country": conf.country, "contactelectronicmailaddress": conf.electronic_mail_address, "contactfacsimiletelephone": conf.phone_facsimile, "contactvoicetelephone": conf.phone_voice, "contactperson": conf.individual_name, "contactorganization": conf.provider_name, "contactposition": conf.position_name, "fees": conf.fees, "keywordlist": ",".join(conf.keywords), "srs": " ".join( crss.getSupportedCRS_WCS( format_function=crss.asShortCode)), }, namespace="ows") map_.setProjection("EPSG:4326") map_.setMetaData( { "getmap_formatlist": ",".join([f.mimeType for f in self.get_wms_formats()]), "getfeatureinfo_formatlist": "text/html,application/vnd.ogc.gml,text/plain", }, namespace="wms") map_extent = None for collection in collections: group_name = None # calculate extent and timextent for every collection extent = collection.extent_wgs84 # save overall map extent map_extent = self.join_extents(map_extent, extent) eo_objects = collection.eo_objects.filter(begin_time__isnull=False, end_time__isnull=False) timeextent = ",".join( map( lambda o: ("/".join(map(isoformat, o.time_extent)) + "/PT1S"), eo_objects)) if len(suffixes) > 1: # create group layer, if there is more than one suffix for this # collection group_name = collection.identifier + "_group" group_layer = Layer(group_name) group_layer.setMetaData( { "title": group_name, "abstract": group_name, "extent": " ".join(map(str, extent)), }, namespace="wms") minx, miny, maxx, maxy = extent group_layer.setExtent(minx, miny, maxx, maxy) # add default style default_class = Class("default") default_style = Style("default") default_class.insertStyle(default_style) group_layer.insertClass(default_class) map_.insertLayer(group_layer) for suffix in suffixes: layer_name = collection.identifier + (suffix or "") layer = Layer(layer_name) if group_name: layer.setMetaData({"layer_group": "/" + group_name}, namespace="wms") layer.setMetaData( { "title": layer_name, "abstract": layer_name, "extent": " ".join(map(str, extent)), "timeextent": timeextent, }, namespace="wms") map_.insertLayer(layer) for coverage in coverages: extent = coverage.extent_wgs84 minx, miny, maxx, maxy = extent # save overall map extent map_extent = self.join_extents(map_extent, extent) layer_name = coverage.identifier layer = Layer(layer_name) layer.setMetaData( { "title": layer_name, "abstract": layer_name, "extent": " ".join(map(str, extent)), }, namespace="wms") minx, miny, maxx, maxy = extent layer.setExtent(minx, miny, maxx, maxy) map_.insertLayer(layer) # set the map_extent to a reasonable default value # in case there is no coverage or collection if map_extent is None: map_extent = (0.0, 0.0, 1.0, 1.0) map_minx, map_miny, map_maxx, map_maxy = map_extent map_.setExtent(map_minx, map_miny, map_maxx, map_maxy) request = create_request(request_values) raw_result = map_.dispatch(request) result = result_set_from_raw_data(raw_result) return result, get_content_type(result) def get_wms_formats(self): return getFormatRegistry().getSupportedFormatsWMS() def join_extents(self, e1=None, e2=None): if e1 and e2: e1_minx, e1_miny, e1_maxx, e1_maxy = e1 e2_minx, e2_miny, e2_maxx, e2_maxy = e2 return (min(e1_minx, e2_minx), min(e1_miny, e2_miny), max(e1_maxx, e2_maxx), max(e1_maxy, e2_maxy)) elif e1: return e1 elif e2: return e2 else: return None