예제 #1
0
def from_request_bbox(
        bbox: str,
        target_crs: gws.ICrs = None,
        invert_axis_if_geographic: bool = False) -> t.Optional[gws.Bounds]:
    """Create Bounds from a KVP BBOX param.

    See:
        OGC 06-121r9, 10.2.3 Bounding box KVP encoding
    """

    if not bbox:
        return None

    source_crs = target_crs

    # x,y,x,y,crs
    ls = bbox.split(',')
    if len(ls) == 5:
        source_crs = gws.gis.crs.get(ls.pop())

    if not source_crs:
        return None

    if source_crs.is_geographic and invert_axis_if_geographic:
        ext = gws.gis.extent.from_inverted_str_list(ls)
    else:
        ext = gws.gis.extent.from_str_list(ls)
    if not ext:
        return None

    if target_crs:
        return gws.Bounds(crs=target_crs,
                          extent=source_crs.transform_extent(ext, target_crs))

    return gws.Bounds(crs=source_crs, extent=ext)
예제 #2
0
def _map_view(bbox, center, crs, dpi, rotation, scale, size):
    view = gws.MapView(
        crs=crs,
        dpi=dpi,
        rotation=rotation,
    )

    w, h, u = size
    if u == units.MM:
        view.size_mm = w, h
        view.size_px = units.size_mm_to_px(view.size_mm, view.dpi)
    if u == units.PX:
        view.size_px = w, h
        view.size_mm = units.size_px_to_mm(view.size_px, view.dpi)

    if bbox:
        view.bounds = gws.Bounds(crs=crs, extent=bbox)
        view.center = gws.gis.extent.center(bbox)
        bw, bh = gws.gis.extent.size(bbox)
        view.scale = units.res_to_scale(bw / view.size_px[0])

    elif center:
        view.center = center
        view.scale = scale

        # @TODO assuming projection units are 'm'
        projection_units_per_mm = scale / 1000.0
        size = view.size_mm[0] * projection_units_per_mm, view.size_mm[
            1] * projection_units_per_mm
        bbox = gws.gis.extent.from_center(center, size)
        view.bounds = gws.Bounds(crs=crs, extent=bbox)

    return view
예제 #3
0
def parse_envelope(el: gws.XmlElement,
                   fallback_crs: gws.ICrs = None) -> gws.Bounds:
    """Parse a gml:Box/gml:Envelope element"""

    # GML2: <gml:Box><gml:coordinates>1,2 3,4
    # GML3: <gml:Envelope srsDimension="2"><gml:lowerCorner>1 2  <gml:upperCorner>3 4

    crs = gws.gis.crs.get(xml2.attr(el, 'srsName')) or fallback_crs
    if not crs:
        raise Error('envelope: no CRS')

    name = _pname(el)
    coords = None

    try:
        if name == 'box':
            coords = _coords(el)

        elif name == 'Envelope':
            coords = []
            for c in el.children:
                cname = _pname(c)
                if cname == 'lowercorner':
                    coords[0] = _coords_pos(c)[0]
                if cname == 'uppercorner':
                    coords[1] = _coords_pos(c)[0]

        return gws.Bounds(crs=crs, extent=gws.gis.extent.from_points(*coords))

    except Exception as exc:
        raise Error('envelope: parse error') from exc
예제 #4
0
    def render_svg_fragment(self, view, style=None):
        bounds = view.bounds
        if view.rotation:
            bounds = gws.Bounds(crs=view.bounds.crs,
                                extent=gws.gis.extent.circumsquare(
                                    bounds.extent))

        ts = gws.time_start('render_svg:get_features')
        found = self.get_features(bounds)
        gws.time_end(ts)

        ts = gws.time_start('render_svg:convert')
        for f in found:
            f.transform_to(bounds.crs)
            f.apply_templates(subjects=['label'])
        gws.time_end(ts)

        ts = gws.time_start('render_svg:to_svg')
        tags = [
            tag for f in found
            for tag in f.to_svg_fragment(view, style or self.style)
        ]
        gws.time_end(ts)

        return tags
예제 #5
0
    def _bounds_for_tile(self, matrix_set_uid, matrix_uid, row, col):
        tms = None
        tm = None

        for m in self.tile_matrix_sets:
            if m.uid == matrix_set_uid:
                tms = m

        if not tms:
            return

        for m in tms.matrices:
            if m.uid == matrix_uid:
                tm = m

        if not tm:
            return

        w, h = gws.gis.extent.size(tm.extent)
        span = w / tm.width

        x = tm.x + col * span
        y = tm.y - row * span

        bbox = x, y - span, x + span, y
        return gws.Bounds(crs=tms.crs, extent=bbox)
예제 #6
0
 def own_bounds(self):
     if not self.table.geometry_column:
         return None
     with self.provider.connect() as conn:
         r = conn.select_value('SELECT ST_Extent({:name}) FROM {:qname}',
                               self.table.geometry_column.name,
                               self.table.name)
     if not r:
         return None
     return gws.Bounds(crs=gws.gis.crs.get(self.table.geometry_column.srid),
                       extent=gws.gis.extent.from_box(r))
예제 #7
0
def search_args(layer):
    return gws.SearchArgs(bounds=gws.Bounds(crs='EPSG:3857',
                                            extent=[100, 200, 300, 400]),
                          resolution=10,
                          layers=[layer],
                          shapes=[
                              gws.gis.shape.from_geometry(
                                  {
                                      'type': 'point',
                                      'coordinates': [100, 200]
                                  }, 'EPSG:3857')
                          ])
예제 #8
0
    def _get_features(self, req: gws.IWebRequest, p: GetFeaturesParams) -> t.List[gws.IFeature]:
        layer = req.require_layer(p.layerUid)
        bounds = gws.Bounds(
            crs=gws.gis.crs.get(p.crs) or layer.map.crs,
            extent=p.get('bbox') or layer.map.extent
        )

        found = layer.get_features(bounds, p.get('limit'))

        for f in found:
            f.transform_to(bounds.crs)
            f.apply_templates(subjects=['label', 'title'])
            f.apply_data_model()

        return found
예제 #9
0
def combined_bounds(layers: t.List[gws.SourceLayer],
                    target_crs: gws.ICrs) -> gws.Bounds:
    """Return merged bounds from a list of source layers in the target_crs."""

    exts = []

    for sl in layers:
        if not sl.supported_bounds:
            continue
        bb = gws.gis.crs.best_bounds(target_crs, sl.supported_bounds)
        ext = gws.gis.extent.transform(bb.extent, bb.crs, target_crs)
        exts.append(ext)

    if exts:
        return gws.Bounds(crs=target_crs, extent=gws.gis.extent.merge(exts))
예제 #10
0
    def find_features(self, req: gws.IWebRequest, p: Params) -> Response:
        """Perform a search"""

        project = req.require_project(p.projectUid)

        bounds = gws.Bounds(
            crs=p.crs or project.map.crs,
            extent=p.bbox or project.map.extent,
        )

        limit = self.limit
        if p.limit:
            limit = min(int(p.limit), self.limit)

        shapes = []
        if p.shapes:
            shapes = [gws.gis.shape.from_props(s) for s in p.shapes]

        tolerance = None
        if p.tolerance:
            tolerance = gws.lib.units.parse(p.tolerance,
                                            default=gws.lib.units.PX)

        args = gws.SearchArgs(
            bounds=bounds,
            keyword=(p.keyword or '').strip(),
            layers=gws.compact(
                req.acquire('gws.ext.layer', uid) for uid in p.layerUids),
            limit=limit,
            project=project,
            resolution=p.resolution,
            shapes=shapes,
            tolerance=tolerance,
        )

        found = runner.run(req, args)

        for f in found:
            # @TODO only pull specified props from a feature
            f.transform_to(args.bounds.crs)
            f.apply_templates()
            f.apply_data_model()

        return Response(
            features=[gws.props(f, req.user, context=self) for f in found])
예제 #11
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
예제 #12
0
    def render_box(self, view, extra_params=None):
        uid = self.uid
        if not self.has_cache:
            uid += '_NOCACHE'

        if not view.rotation:
            return gws.gis.mpx.wms_request(uid,
                                           view.bounds,
                                           view.size_px[0],
                                           view.size_px[1],
                                           forward=extra_params)

        # rotation: render a circumsquare around the wanted extent

        circ = gws.gis.extent.circumsquare(view.bounds.extent)
        w, h = view.size_px
        d = gws.gis.extent.diagonal((0, 0, w, h))

        r = gws.gis.mpx.wms_request(uid,
                                    gws.Bounds(crs=view.bounds.crs,
                                               extent=circ),
                                    width=d,
                                    height=d,
                                    forward=extra_params)
        if not r:
            return

        img = gws.lib.image.from_bytes(r)

        # rotate the square (NB: PIL rotations are counter-clockwise)
        # and crop the square back to the wanted extent

        img.rotate(-view.rotation).crop((
            d / 2 - w / 2,
            d / 2 - h / 2,
            d / 2 + w / 2,
            d / 2 + h / 2,
        ))

        return img.to_bytes()
예제 #13
0
    def _populate_layer_caps(self, lc: LayerCaps, layer: gws.ILayer, lo: LayerOptions, children: t.List[LayerCaps]):
        lc.layer = layer
        lc.title = layer.title

        lc.layer_qname = xml2.qualify_name(lo.layer_name, _DEFAULT_NAMESPACE_PREFIX)
        lc.layer_pname = xml2.unqualify_name(lc.layer_qname)

        lc.feature_qname = xml2.qualify_name(lo.feature_name, _DEFAULT_NAMESPACE_PREFIX)
        lc.feature_pname = xml2.unqualify_name(lc.feature_qname)

        lc.meta = layer.metadata.values
        lc.children = children

        lc.extent = layer.extent
        lc.extent4326 = gws.gis.extent.transform_to_4326(layer.extent, layer.crs)
        lc.has_legend = layer.has_legend or any(s.has_legend for s in lc.children)
        lc.has_search = layer.has_search or any(s.has_search for s in lc.children)

        scales = [gws.lib.units.res_to_scale(r) for r in layer.resolutions]
        lc.min_scale = int(min(scales))
        lc.max_scale = int(max(scales))

        lc.bounds = [
            gws.Bounds(
                crs=crs,
                extent=gws.gis.extent.transform(layer.extent, layer.crs, crs)
            )
            for crs in self.supported_crs or [layer.crs]
        ]

        pfx, n = xml2.split_name(lc.feature_qname)

        if not xml2.namespaces.schema(pfx) and layer.data_model:
            lc.adhoc_feature_schema = layer.data_model.xml_schema_dict(name_for_geometry=_DEFAULT_GEOMETRY_NAME)

        return lc
예제 #14
0
def prepared_search(**kwargs) -> PreparedOwsSearch:
    ps = PreparedOwsSearch(kwargs)

    params = {}

    wms_box_size_m = 500
    wms_box_size_deg = 1
    wms_box_size_px = 500

    if ps.protocol == gws.OwsProtocol.WMS:
        s = wms_box_size_m if ps.point.crs.is_projected else wms_box_size_deg
        bbox = (
            ps.point.x - (s >> 1),
            ps.point.y - (s >> 1),
            ps.point.x + (s >> 1),
            ps.point.y + (s >> 1),
        )
        ps.bounds = gws.Bounds(crs=ps.point.crs, extent=bbox)

    our_crs = ps.bounds.crs

    ps.request_crs = ps.request_crs or gws.gis.crs.best_match(
        our_crs, gws.gis.source.supported_crs_list(ps.source_layers))

    bbox = gws.gis.extent.transform(ps.bounds.extent, our_crs, ps.request_crs)

    ps.axis = gws.gis.crs.best_axis(ps.request_crs,
                                    protocol=ps.protocol,
                                    protocol_version=ps.protocol_version,
                                    inverted_crs=ps.inverted_crs)
    if ps.axis == gws.AXIS_YX:
        bbox = gws.gis.extent.swap_xy(bbox)

    layer_names = [sl.name for sl in ps.source_layers]

    if ps.protocol == gws.OwsProtocol.WMS:
        v3 = ps.protocol_version >= '1.3'
        params = {
            'BBOX': bbox,
            'CRS' if v3 else 'SRS':
            ps.request_crs.to_string(ps.request_crs_format),
            'WIDTH': wms_box_size_px,
            'HEIGHT': wms_box_size_px,
            'I' if v3 else 'X': wms_box_size_px >> 1,
            'J' if v3 else 'Y': wms_box_size_px >> 1,
            'LAYERS': layer_names,
            'QUERY_LAYERS': layer_names,
            'STYLES': [''] * len(layer_names),
            'VERSION': ps.protocol_version,
        }
        if ps.limit:
            params['FEATURE_COUNT'] = ps.limit

    if ps.protocol == gws.OwsProtocol.WFS:
        v2 = ps.protocol_version >= '2.0.0'
        params = {
            'BBOX': bbox,
            'SRSNAME': ps.request_crs.to_string(ps.request_crs_format),
            'TYPENAMES' if v2 else 'TYPENAME': layer_names,
            'VERSION': ps.protocol_version,
        }
        if ps.limit:
            params['COUNT' if v2 else 'MAXFEATURES'] = ps.limit

    ps.params = params
    return ps
예제 #15
0
 def bounds(self) -> gws.Bounds:
     return gws.Bounds(crs=self.crs, extent=self.extent)
예제 #16
0
def render_view(layer):
    return gws.MapView(
        bounds=gws.Bounds(crs='EPSG:3857', extent=[100, 200, 300, 400]),
        dpi=0,
        size_px=(100, 100),
    )
예제 #17
0
 def own_bounds(self):
     # in the "native" projection, use the service extent
     # otherwise, the map extent
     if self.service.crs == self.crs:
         return gws.Bounds(crs=self.service.crs, extent=self.service.extent)
예제 #18
0
def transformed_to(b: gws.Bounds, target_crs: gws.ICrs) -> gws.Bounds:
    if b.crs == target_crs:
        return b

    return gws.Bounds(crs=target_crs,
                      extent=b.crs.transform_extent(b.extent, target_crs))
예제 #19
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())