Example #1
0
def _metadata_dict(el: gws.XmlElement) -> dict:
    if not el:
        return {}

    d = {
        'abstract':
        xml2.text(el, 'Abstract'),
        'accessConstraints':
        xml2.text(el, 'AccessConstraints'),
        'attribution':
        xml2.text(el, 'Attribution Title'),
        'fees':
        xml2.text(el, 'Fees'),
        'keywords':
        xml2.text_list(el, 'Keywords', 'KeywordList', deep=True),
        'name':
        xml2.text(el, 'Name') or xml2.text(el, 'Identifier'),
        'title':
        xml2.text(el, 'Title'),
        'metaLinks':
        gws.compact(_parse_link(e) for e in xml2.all(el, 'MetadataURL')),
    }

    e = xml2.first(el, 'AuthorityURL')
    if e:
        d['authorityUrl'] = _parse_url(e)
        d['authorityName'] = xml2.attr(e, 'name')

    e = xml2.first(el, 'Identifier')
    if e:
        d['authorityIdentifier'] = e.text

    return gws.strip(d)
Example #2
0
def service_metadata(root_el: gws.XmlElement) -> gws.lib.metadata.Metadata:
    # wms
    #
    #   <Capabilities
    #       <Service...
    #           <Name>...
    #           <Title>...
    #           <ContactInformation>...
    #
    # ows
    #
    #   <Capabilities
    #       <ows:ServiceIdentification>
    #           <ows:Title>....
    #       <ows:ServiceProvider>
    #           <ows:ProviderName>...
    #           <ows:ServiceContact>...

    d = _metadata_dict(xml2.first(root_el, 'Service', 'ServiceIdentification'))
    d.update(_contact_dict(root_el))
    d['contactProviderName'] = xml2.text(root_el,
                                         'ServiceProvider ProviderName')
    d['contactProviderSite'] = xml2.text(root_el,
                                         'ServiceProvider ProviderSite')

    #   <Capabilities
    #       <ServiceMetadataURL

    link = _parse_link(xml2.first(root_el, 'ServiceMetadataURL'))
    if link:
        d['metaLinks'] = [link]

    return gws.lib.metadata.from_dict(gws.strip(d))
Example #3
0
 def _find_records(self, rd: core.Request):
     flt = None
     if rd.xml_element:
         flt = xml2.first(xml2.first('Query.Constraint.Filter'))
     if not flt:
         return self.records.values()
     f = filter.Filter(self.index)
     return f.apply(flt, self.records.values())
Example #4
0
def from_fes_element(el: gws.XmlElement) -> gws.SearchFilter:
    op = el.name.lower()

    if op == 'filter':
        # root element, only allow a single child predicate
        if len(el.children) != 1:
            raise Error(f'invalid root predicate')
        return from_fes_element(el.children[0])

    if op in ('and', 'or'):
        return gws.SearchFilter(operator=op, sub=[from_fes_element(c) for c in el.children])

    if op not in _SUPPORTED_OPS:
        raise Error(f'unsupported filter operation {el.name!r}')

    f = gws.SearchFilter(
        operator=_SUPPORTED_OPS[op],
    )

    # @TODO support "prop = prop"

    v = xml2.first(el, 'ValueReference', 'PropertyName')
    if not v or not v.text:
        raise Error(f'invalid property name')

    # we only support `propName` or `ns:propName`
    m = re.match(r'^(\w+:)?(\w+)$', v.text)
    if not m:
        raise Error(f'invalid property name {v.text!r}')
    f.name = m.group(2)

    if op == 'bbox':
        v = xml2.first(el, 'Envelope')
        if v:
            bounds = gws.gis.bounds.from_gml_envelope_element()
            f.shape = gws.gis.shape.from_bounds(bounds)
            return f

    v = xml2.first(el, 'Literal')
    if v:
        f.value = v.text.strip()
        return f

    raise Error(f'unsupported filter')
Example #5
0
def _parse_bbox(el: gws.XmlElement):
    # note: bboxes are always converted to (x1, y1, x2, y2) with x1 < x2, y1 < y2

    # <BoundingBox/LatLonBoundingBox CRS="..." minx="0" miny="1" maxx="2" maxy="3"/>

    if xml2.attr(el, 'minx'):
        return [
            to_float(xml2.attr(el, 'minx')),
            to_float(xml2.attr(el, 'miny')),
            to_float(xml2.attr(el, 'maxx')),
            to_float(xml2.attr(el, 'maxy')),
        ]

    # <ows:BoundingBox/WGS84BoundingBox
    #       <ows:LowerCorner> 0 1
    #       <ows:UpperCorner> 2 3

    if xml2.first(el, 'LowerCorner'):
        x1, y1 = to_float_pair(xml2.text(el, 'LowerCorner'))
        x2, y2 = to_float_pair(xml2.text(el, 'UpperCorner'))
        return [
            min(x1, x2),
            min(y1, y2),
            max(x1, x2),
            max(y1, y2),
        ]

    # <EX_GeographicBoundingBox>
    #       <westBoundLongitude> 0
    #       <eastBoundLongitude> 2
    #       <southBoundLatitude> 1
    #       <northBoundLatitude> 3

    if xml2.first(el, 'westBoundLongitude'):
        x1 = to_float(xml2.text(el, 'eastBoundLongitude'))
        y1 = to_float(xml2.text(el, 'southBoundLatitude'))
        x2 = to_float(xml2.text(el, 'westBoundLongitude'))
        y2 = to_float(xml2.text(el, 'northBoundLatitude'))
        return [
            min(x1, x2),
            min(y1, y2),
            max(x1, x2),
            max(y1, y2),
        ]
Example #6
0
def _parse_operation(el: gws.XmlElement) -> gws.OwsOperation:
    params = {}

    for param_el in xml2.all(el, 'Parameter'):
        values = xml2.text_list(param_el, 'Value', 'AllowedValues Value')
        if values:
            params[xml2.attr(param_el, 'name')] = values

    op = gws.OwsOperation(
        verb=xml2.attr(el, 'name') or el.name,
        formats=xml2.text_list(el, 'Format'),
        get_url=_parse_url(xml2.first(el, 'DCP HTTP Get', 'DCPType HTTP Get')),
        post_url=_parse_url(
            xml2.first(el, 'DCP HTTP Post', 'DCPType HTTP Post')),
        params=params,
    )

    if 'outputFormat' in params:
        op.formats.extend(params['outputFormat'])

    return op
Example #7
0
    def handle_request(self, req: gws.IWebRequest) -> gws.ContentResponse:
        rd = core.Request(req=req, project=None, service=self)

        if req.method == 'GET':
            return self.dispatch_request(
                rd, req.param('request', default='record'))

        # CSW should accept POST'ed xml, which can be wrapped in a SOAP envelope

        try:
            rd.xml_element = xml2.from_string(req.text)
        except xml2.Error:
            raise gws.base.web.error.BadRequest()

        if rd.xml_element.name.lower() == 'envelope':
            rd.xml_is_soap = True
            try:
                rd.xml_element = xml2.first(xml2.first('body'))
            except Exception:
                raise gws.base.web.error.BadRequest()

        return self.dispatch_request(
            rd, xml2.unqualify_name(rd.xml_element.name.lower()))
Example #8
0
def parse(xml: str) -> Caps:
    root_el = xml2.from_string(xml,
                               sort_atts=True,
                               strip_ns=True,
                               to_lower=True)

    ver = root_el.attributes.get('version', '').split('-')[0]
    if not ver.startswith('3'):
        raise gws.Error(f'unsupported QGIS version {ver!r}')

    caps = Caps(version=ver)

    caps.properties = _properties(xml2.first(root_el, 'properties'))
    caps.metadata = _project_meta_from_props(caps.properties)
    caps.project_crs = gws.gis.crs.get(
        xml2.text(root_el, 'projectCrs spatialrefsys authid'))
    caps.print_templates = _layouts(root_el)

    map_layers = _map_layers(root_el, caps.properties)
    root_group = _map_layer_tree(xml2.first(root_el, 'layer-tree-group'),
                                 map_layers)
    caps.source_layers = gws.gis.source.check_layers(root_group.layers)

    return caps
Example #9
0
def _contact_dict(el: gws.XmlElement) -> dict:
    contact_el = xml2.first(el, 'Service ContactInformation',
                            'ServiceProvider ServiceContact')
    if not contact_el:
        return {}

    texts = xml2.text_dict(contact_el, deep=True)
    d = {}

    for dst, src in _contact_mapping:
        val = texts.get(src, '').strip()
        if val:
            d[dst] = val

    return d
Example #10
0
def parse_style(el: gws.XmlElement) -> gws.SourceStyle:
    # <Style>
    #     <Name>default...
    #     <Title>...
    #     <LegendURL
    #         <Format>...
    #         <OnlineResource...

    st = gws.SourceStyle()

    st.metadata = element_metadata(el)
    st.name = st.metadata.get('name', '').lower()
    st.legend_url = _parse_url(xml2.first(el, 'LegendURL'))
    st.is_default = (xml2.attr(el, 'IsDefault') == 'true'
                     or st.name == 'default' or st.name.endswith(':default'))
    return st
Example #11
0
def _layouts(root_el: gws.XmlElement):
    tpls = []

    for layout_el in xml2.all(root_el, 'Layouts Layout'):
        tpl = PrintTemplate(
            title=layout_el.attributes.get('name', ''),
            attributes=layout_el.attributes,
            index=len(tpls),
            elements=[],
        )
        pc_el = xml2.first(layout_el, 'PageCollection')
        if pc_el:
            tpl.elements.extend(
                gws.compact(_layout_element(c) for c in pc_el.children))
        tpl.elements.extend(
            gws.compact(_layout_element(c) for c in layout_el.children))

        tpls.append(tpl)

    return tpls
Example #12
0
def _parse_url(el: gws.XmlElement) -> str:
    def cleanup(s):
        return (s or '').strip(' ?&')

    # <ows:DCP>
    #       <ows:HTTP>
    #           <ows:Get xlink:href=... <-- we are here

    s = xml2.attr(el, 'href') or xml2.attr(el, 'onlineResource')
    if s:
        return cleanup(s)

    # <whatever <--
    #       <OnlineResource xlink:href=...

    e = xml2.first(el, 'OnlineResource')
    if e:
        return cleanup(xml2.attr(e, 'href', default=e.text))

    return ''
Example #13
0
def _map_layer(layer_el: gws.XmlElement):
    sl = gws.SourceLayer()

    sl.metadata = _map_layer_metadata(layer_el)

    sl.supported_bounds = []

    crs = gws.gis.crs.get(xml2.text(layer_el, 'srs spatialrefsys authid'))
    ext = xml2.first(layer_el, 'extent')

    if crs and ext:
        sl.supported_bounds.append(
            gws.Bounds(crs=crs,
                       extent=(
                           _parse_float(xml2.text(ext, 'xmin')),
                           _parse_float(xml2.text(ext, 'ymin')),
                           _parse_float(xml2.text(ext, 'xmax')),
                           _parse_float(xml2.text(ext, 'ymax')),
                       )))

    if layer_el.attributes.get('hasScaleBasedVisibilityFlag') == '1':
        # in qgis, maxScale < minScale
        a = _parse_float(layer_el.attributes.get('maxScale'))
        z = _parse_float(layer_el.attributes.get('minScale'))
        if z > a:
            sl.scale_range = [a, z]

    prov = xml2.text(layer_el, 'provider').lower()
    ds = _parse_datasource(prov, xml2.text(layer_el, 'datasource'))
    if ds and 'provider' not in ds:
        ds['provider'] = prov
    sl.data_source = ds

    s = xml2.text(layer_el, 'layerOpacity')
    if s:
        sl.opacity = _parse_float(s)

    s = xml2.text(layer_el, 'flags Identifiable')
    sl.is_queryable = s == '1'

    return sl
Example #14
0
def service_operations(root_el: gws.XmlElement) -> t.List[gws.OwsOperation]:
    # <ows:OperationsMetadata>
    #     <ows:Operation name="GetCapabilities">
    #         <ows:DCP><ows:HTTP><ows:Get xlink:href="...."/></ows:HTTP></ows:DCP>
    #         <ows:Parameter name="AcceptVersions">
    #             <ows:AllowedValues><ows:Value>2.0.0</ows:Value></ows:AllowedValues>

    els = xml2.all(root_el, 'OperationsMetadata Operation')
    if els:
        return [_parse_operation(e) for e in els]

    # <Capability>
    #   <Request>
    #     <GetMap>
    #       <Format>image/png</Format>
    #       <Format>application/atom xml</Format>
    #       <DCPType><HTTP><Get><OnlineResource ....

    el = xml2.first(root_el, 'Capability Request')
    if el:
        return [_parse_operation(e) for e in el.children]

    return []
Example #15
0
def supported_bounds(
        layer_el: gws.XmlElement,
        extra_crsids: t.Optional[t.List[str]] = None) -> t.List[gws.Bounds]:
    # <Layer...
    #     <CRS>EPSG....
    #     <EX_GeographicBoundingBox...
    #     <BoundingBox CRS="EPSG:" minx=....
    #
    # <FeatureType...
    #     <DefaultCRS>urn:ogc:def:crs:EPSG...
    #     <OtherCRS>urn:ogc:def:crs:EPSG...
    #     <ows:WGS84BoundingBox>
    #         <ows:LowerCorner...
    #         <ows:UpperCorner...

    if not layer_el:
        return []

    crs_to_bounds = {}

    # enumerate explicitly listed bounds (WMS)

    for e in xml2.all(layer_el, 'BoundingBox'):
        crs = gws.gis.crs.get(xml2.attr(e, 'srs') or xml2.attr(e, 'crs'))
        bbox = _parse_bbox(e)
        if crs and bbox:
            crs_to_bounds[crs] = gws.Bounds(crs=crs, extent=bbox)

    # NB prefer these for 4326 to avoid axis issues

    e = xml2.first(layer_el, 'EX_GeographicBoundingBox', 'WGS84BoundingBox',
                   'LatLonBoundingBox')
    if e:
        bbox = _parse_bbox(e)
        if bbox:
            wgs = gws.gis.crs.get4326()
            crs_to_bounds[wgs] = gws.Bounds(crs=wgs, extent=bbox)

    # no bounds

    if not crs_to_bounds:
        return []

    # collect other supported crs and add extras (e.g. wmts matrix sets)

    crsids = set()

    for tag in 'DefaultSRS', 'DefaultCRS', 'OtherSRS', 'OtherCRS', 'SRS', 'CRS':
        for e in xml2.all(layer_el, tag):
            if e.text:
                crsids.add(e.text)

    if extra_crsids:
        crsids.update(extra_crsids)

    # freeze the bounds list to prevent double reprojection

    bs = list(crs_to_bounds.values())

    # compute bounds for those without bounds

    for crsid in crsids:
        new_crs = gws.gis.crs.get(crsid)
        if not new_crs or new_crs in crs_to_bounds:
            continue
        bb = gws.gis.crs.best_bounds(new_crs, bs)
        try:
            new_ext = gws.gis.extent.transform(bb.extent, bb.crs, new_crs)
        except Exception as exc:
            gws.log.error(f'failed transform {bb.crs.srid}=>{new_crs.srid}')
            continue
        crs_to_bounds[new_crs] = gws.Bounds(crs=new_crs, extent=new_ext)

    return list(crs_to_bounds.values())