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)
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
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
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
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)
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))
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') ])
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
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))
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])
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
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()
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
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
def bounds(self) -> gws.Bounds: return gws.Bounds(crs=self.crs, extent=self.extent)
def render_view(layer): return gws.MapView( bounds=gws.Bounds(crs='EPSG:3857', extent=[100, 200, 300, 400]), dpi=0, size_px=(100, 100), )
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)
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))
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())