def props_for(self, user): p = super().props_for(user) if self.display == 'box': return gws.merge(p, type='box', url=core.layer_url_path(self.uid, kind='box')) return gws.merge(p, type='vector', loadingStrategy=self.var('loadingStrategy'), style=self.style, editStyle=self.edit_style, url=core.layer_url_path(self.uid, kind='features'))
def props_for(self, user): if not self.provider.has_index: return None with_eigentuemer = self._can_read_eigentuemer(user) with_buchung = self._can_read_buchung(user) export_groups = [] for i, group in enumerate(self.export_groups): if group.with_eigentuemer and not with_eigentuemer: continue if group.with_buchung and not with_buchung: continue export_groups.append(ExportGroupProps(index=i, title=group.title)) return gws.merge( super().props_for(user), exportGroups=sorted(export_groups, key=lambda g: g.title), gemarkungen=self.provider.gemarkung_list(), limit=self.limit, printTemplate=self.print_template, ui=self.ui, withBuchung=with_buchung, withControl=with_eigentuemer and self.control_mode, withEigentuemer=with_eigentuemer, withFlurnummer=self.provider.has_flurnummer, )
def template_response(self, rd: Request, verb: gws.OwsVerb, format: str = None, context=None): mime = None if format: mime = gws.lib.mime.get(format) if not mime: gws.log.debug(f'no mimetype: verb={verb!r} format={format!r}') raise gws.base.web.error.BadRequest('Invalid FORMAT') tpl = self.templates.find(category='ows', name=str(verb), mime=mime) if not tpl: gws.log.debug(f'no template: verb={verb!r} format={format!r}') raise gws.base.web.error.BadRequest('Unsupported FORMAT') gws.log.debug(f'template found: verb={verb!r} format={format!r} tpl={tpl.uid!r}') context = gws.merge({ 'project': rd.project, 'service': self, 'service_meta': self.metadata.values, 'service_url': rd.req.url_for(self.service_url_path(rd.project)), 'url_for': rd.req.url_for, 'with_inspire_meta': self.with_inspire_meta, }, context) return tpl.render(gws.TemplateRenderInput(context=context))
def configure(self): self.crs = gws.gis.crs.get(self.var('crs')) self.index_schema = self.var('indexSchema') self.data_schema = self.var('dataSchema') db = gws.plugin.postgres.provider.require_for(self) self.connect_params = gws.merge( {}, db.config, index_schema=self.index_schema, data_schema=self.data_schema, crs=self.crs, exclude_gemarkung=self.var('excludeGemarkung'), ) with self.connection() as conn: if 'ax_flurstueck' in conn.table_names(self.data_schema): gws.log.debug(f'ALKIS sources in {db.uid!r} found') self.has_source = True else: gws.log.warn(f'ALKIS sources in {db.uid!r} NOT found') if index.ok(conn): gws.log.debug(f'ALKIS indexes in {db.uid!r} found') self.has_index = True self.has_flurnummer = flurstueck.has_flurnummer(conn) else: gws.log.warn(f'ALKIS indexes in {db.uid!r} NOT found')
def _finalize_pdf(self, tri, html, parser): content_path = gws.tempname('final.pdf') has_frame = parser.header or parser.footer gws.lib.html2.render_to_pdf( html, out_path=content_path, page_size=parser.page_size, page_margin=parser.page_margin, ) if not has_frame: return content_path context = gws.merge(tri.context, page_count=gws.lib.pdf.page_count(content_path)) frame_text = self._page_frame_template(parser.header or '', parser.footer or '', parser.page_size) frame_html = self._do_render(frame_text, context, None, None) frame_path = gws.tempname('frame.pdf') gws.lib.html2.render_to_pdf( frame_html, out_path=frame_path, page_size=parser.page_size, page_margin=None, ) comb_path = gws.tempname('comb.pdf') gws.lib.pdf.overlay(frame_path, content_path, comb_path) return comb_path
def apply_templates(self, templates=None, extra_context=None, subjects=None): templates = templates or self.templates or (self.layer.templates if self.layer else None) if not templates: return self used = set() tri = gws.TemplateRenderInput( context=gws.merge(self.template_context, extra_context)) if subjects: templates = [ tpl for tpl in templates.items if tpl.subject in subjects ] else: templates = [ tpl for tpl in templates.items if tpl.category == 'feature' ] for tpl in templates: if tpl.name not in used: self.elements[tpl.name] = tpl.render(tri).content used.add(tpl.name) return self
def walk(sl: gws.SourceLayer, depth: int): if exclude_slf and gws.gis.source.layer_matches(sl, exclude_slf): return None cfg = None if not sl.is_group: # leaf layer cfg = leaf_layer_config_fn([sl]) elif flatten and sl.a_level >= flatten.level: # flattened group layer # NB use the absolute level to compute flatness, could also use relative (=depth) if flatten.useGroups: cfg = leaf_layer_config_fn([sl]) else: slf = gws.gis.source.LayerFilter(is_image=True) image_layers = gws.gis.source.filter_layers([sl], slf) if not image_layers: return None cfg = leaf_layer_config_fn(image_layers) else: # ordinary group layer sub_cfgs = gws.compact(walk(sub, depth + 1) for sub in sl.layers) if not sub_cfgs: return None cfg = { 'type': 'group', 'uid': gws.to_uid(sl.name), 'layers': sub_cfgs } if not cfg: return cfg = gws.merge( cfg, { 'uid': gws.to_uid(sl.name), 'title': sl.title, 'clientOptions': { 'visible': sl.is_visible, 'expanded': sl.is_expanded, }, 'opacity': sl.opacity or 1, }) if sl.scale_range: cfg['zoom'] = { 'minScale': sl.scale_range[0], 'maxScale': sl.scale_range[1], } for flt, cc in zip(custom_filters, custom_configs): if gws.gis.source.layer_matches(sl, flt): cfg = gws.deep_merge(vars(cc), cfg) cfg.pop('applyTo', None) return gws.compact(cfg)
def environ(root: gws.IRoot): base_dir = gws.ensure_dir(gws.TMP_DIR + '/qqq') # it's all a bit blurry, but the server appears to read 'ini' from OPTIONS_DIR # while the app uses a profile # NB: for some reason, the profile path will be profiles/profiles/default (sic!) gws.ensure_dir('profiles', base_dir) gws.ensure_dir('profiles/default', base_dir) gws.ensure_dir('profiles/default/QGIS', base_dir) gws.ensure_dir('profiles/profiles', base_dir) gws.ensure_dir('profiles/profiles/default', base_dir) gws.ensure_dir('profiles/profiles/default/QGIS', base_dir) ini = _make_ini(root, base_dir) gws.write_file(base_dir + '/profiles/default/QGIS/QGIS3.ini', ini) gws.write_file(base_dir + '/profiles/profiles/default/QGIS/QGIS3.ini', ini) # server options, as documented on # see https://docs.qgis.org/testing/en/docs/user_manual/working_with_ogc/server/config.html#environment-variables server_env = { # not used here 'QGIS_PLUGINPATH': '', # not used here 'QGIS_SERVER_LOG_FILE': '', # see https://github.com/qgis/QGIS/pull/35738 'QGIS_SERVER_IGNORE_BAD_LAYERS': 'true', 'MAX_CACHE_LAYERS': root.application.var('server.qgis.maxCacheLayers'), 'QGIS_OPTIONS_PATH': base_dir + '/profiles/profiles/default', 'QGIS_SERVER_CACHE_DIRECTORY': gws.ensure_dir('servercache', base_dir), 'QGIS_SERVER_CACHE_SIZE': root.application.var('server.qgis.serverCacheSize'), 'QGIS_SERVER_LOG_LEVEL': root.application.var('server.qgis.serverLogLevel'), # 'QGIS_SERVER_MAX_THREADS': 4, # 'QGIS_SERVER_PARALLEL_RENDERING': 'false', } # qgis app options, mostly undocumented qgis_env = { 'QGIS_PREFIX_PATH': '/usr', 'QGIS_DEBUG': root.application.var('server.qgis.debug'), # 'QGIS_GLOBAL_SETTINGS_FILE': '/global_settings.ini', 'QGIS_CUSTOM_CONFIG_PATH': base_dir } # finally, there are lots of GDAL settings, some of those seem relevant # http://trac.osgeo.org/gdal/wiki/ConfigOptions gdal_env = { 'GDAL_FIX_ESRI_WKT': 'GEOGCS', 'GDAL_DEFAULT_WMS_CACHE_PATH': gws.ensure_dir('gdalcache', base_dir), } return gws.merge( server_env, qgis_env, gdal_env, )
def legend_url(self, source_layers, params=None): # qgis legends are rendered bottom-up (rightmost first) # we need the straight order (leftmost first), like in the config std_params = gws.merge( _DEFAULT_LEGEND_PARAMS, { 'MAP': self.path, 'LAYER': ','.join(sl.name for sl in reversed(source_layers)), 'FORMAT': 'image/png', 'TRANSPARENT': True, 'STYLE': '', 'VERSION': '1.1.1', 'DPI': 96, 'SERVICE': gws.OwsProtocol.WMS, 'REQUEST': gws.OwsVerb.GetLegendGraphic, }) return gws.lib.net.add_params(self.url, gws.merge(std_params, params))
def props_for(self, user): p = super().props_for(user) if self.display == 'tile': return gws.merge( p, type='tile', url=core.layer_url_path(self.uid, kind='tile'), tileSize=self.grid.tileSize, ) if self.display == 'box': return gws.merge( p, type='box', url=core.layer_url_path(self.uid, kind='box'), ) return p
def props_for(self, user): ico = self.values.icon if ico and isinstance(ico, icon.ParsedIcon): ico = icon.to_data_url(ico) else: # NB if icon is not parsed, don't give it back ico = '' return gws.Data( values=gws.merge(self.values, icon=ico), name=self.name or '', selector=self.selector or '', )
def find_adresse(self, query: types.FindAdresseQuery, **kwargs) -> types.FindAdresseResult: features = [] with self.connection() as conn: total, rs = adresse.find(conn, gws.merge({}, query, kwargs)) for rec in rs: features.append(gws.gis.feature.Feature( uid=rec['gml_id'], attributes=rec, shape=gws.gis.shape.from_xy(rec['x'], rec['y'], self.crs) )) return types.FindAdresseResult(features=features, total=total)
def configure_layers(obj: gws.IOwsClient, provider_class, **filter_args): if obj.var('_provider'): obj.provider = obj.var('_provider') obj.source_layers = obj.var('_source_layers') else: obj.provider = obj.root.create_object(provider_class, obj.config, shared=True) slf = gws.merge(gws.gis.source.LayerFilter(level=1), filter_args, obj.var('sourceLayers')) obj.source_layers = gws.gis.source.filter_layers( obj.provider.source_layers, slf) if not obj.source_layers: raise gws.Error(f'no source layers found for {obj.uid!r}')
def configure(self): self.metadata = gws.lib.metadata.from_config( self.var('metadata')).extend(self.root.application.metadata) # title at the top level config preferred title = self.var('title') or self.metadata.get('title') or self.var( 'uid') self.metadata.set('title', title) self.title = title self.set_uid(self.var('uid') or gws.to_uid(self.title)) gws.log.info(f'configuring project {self.uid!r}') self.api = self.create_child_if_config(gws.base.api.Object, self.var('api')) self.assets_root = gws.base.web.create_document_root( self.var('assets')) self.locale_uids = self.var('locales', with_parent=True, default=['en_CA']) self.map = self.create_child_if_config(gws.base.map.Object, self.var('map')) self.printer = self.create_child_if_config(gws.base.printer.Object, self.var('printer')) self.overview_map = self.create_child_if_config( gws.base.map.Object, self.var('overviewMap')) if self.overview_map: self.overview_map.set_uid(self.uid + '.overview') self.templates = gws.base.template.bundle.create( self.root, items=self.var('templates'), defaults=_DEFAULT_TEMPLATES, parent=self) self.search_providers = [] p = self.var('search') if p and p.enabled and p.providers: self.search_providers = self.create_children( 'gws.ext.search.provider', p.providers) p = self.var('client') if p: self.client = self.create_child( gws.base.client.Object, gws.merge(p, parentClient=self.parent.var('client')))
def prepare_context(self, context: dict) -> dict: ctx = context or {} ext = { 'gws': { 'version': gws.VERSION, 'endpoint': gws.SERVER_ENDPOINT, } } locale_uid = ctx.get('localeUid') if locale_uid: ext['locale'] = gws.lib.intl.locale(locale_uid) ext['date'] = gws.lib.date.date_formatter(locale_uid) ext['time'] = gws.lib.date.time_formatter(locale_uid) return gws.merge(ext, ctx)
def find_flurstueck(self, query: types.FindFlurstueckQuery, **kwargs) -> types.FindFlurstueckResult: features = [] qdict = self._remove_restricted_params(gws.merge({}, query, kwargs)) with self.connection() as conn: total, rs = flurstueck.find(conn, qdict) for rec in rs: rec = self._remove_restricted_data(qdict, rec) features.append(gws.gis.feature.Feature( uid=rec['gml_id'], attributes=rec, shape=gws.gis.shape.from_wkb_hex(rec['geom'], self.crs) )) return types.FindFlurstueckResult(features=features, total=total)
def find_features(self, args, source_layers): if not args.shapes: return [] shape = args.shapes[0] if shape.geometry_type != gws.GeometryType.point: return [] ps = gws.gis.ows.client.prepared_search( limit=args.limit, point=shape, protocol=gws.OwsProtocol.WMS, protocol_version='1.3.0', request_crs=self.force_crs, request_crs_format=gws.CrsFormat.EPSG, source_layers=source_layers, ) qgis_defaults = { 'INFO_FORMAT': 'text/xml', 'MAP': self.path, # @TODO should be configurable 'FI_LINE_TOLERANCE': 8, 'FI_POINT_TOLERANCE': 16, 'FI_POLYGON_TOLERANCE': 4, # see https://github.com/qgis/qwc2-demo-app/issues/55 'WITH_GEOMETRY': 1, } params = gws.merge(qgis_defaults, ps.params, args.params) text = gws.gis.ows.request.get_text(self.url, gws.OwsProtocol.WMS, gws.OwsVerb.GetFeatureInfo, params=params) features = [] # gws.gis.ows.formats.read(text, crs=ps.request_crs) if features is None: gws.log.debug(f'QGIS/WMS NOT_PARSED params={params!r}') return [] gws.log.debug(f'QGIS/WMS FOUND={len(features)} params={params!r}') return [f.transform_to(shape.crs) for f in features]
def mapproxy_config(self, mc, options=None): layers = [sl.name for sl in self.source_layers if sl.name] if not self.var('capsLayersBottomUp'): layers = reversed(layers) args = self.provider.operation_args(gws.OwsVerb.GetMap) req = gws.merge(args['params'], { 'transparent': True, 'layers': ','.join(layers), 'url': args['url'], }) source_uid = mc.source( gws.compact({ 'type': 'wms', 'supported_srs': [self.source_crs.epsg], 'concurrent_requests': self.var('maxRequests'), 'req': req })) self.mapproxy_layer_config(mc, source_uid)
def _layer_options(self, layer: gws.ILayer, level: int) -> t.Optional[LayerOptions]: if not layer.ows_enabled: return None if self.is_raster_ows and not layer.supports_raster_ows: return None if self.is_vector_ows and not layer.supports_vector_ows: return None defaults = LayerOptions( layer_name=layer.uid.split('.')[-1], feature_name=layer.uid.split('.')[-1], ) for lo in self.layer_options: if lo.level and lo.level != level: continue if lo.uids and layer.uid not in lo.uids: continue if lo.pattern and not re.search(lo.pattern, layer.uid): continue return gws.merge(defaults, lo) if lo.enabled else None return defaults
def find_features(self, args, source_layers): if not args.shapes: return [] shape = args.shapes[0] if shape.geometry_type != gws.GeometryType.point: return [] ps = gws.gis.ows.client.prepared_search( inverted_crs=self.inverted_crs, limit=args.limit, point=shape, protocol=self.protocol, protocol_version=self.version, request_crs=self.force_crs, request_crs_format=gws.CrsFormat.EPSG, source_layers=source_layers, ) params = gws.merge(ps.params, args.params) fmt = self.preferred_formats.get(gws.OwsVerb.GetFeatureInfo) if fmt: params.setdefault('INFO_FORMAT', fmt) op_args = self.operation_args(gws.OwsVerb.GetFeatureInfo, params=params) text = gws.gis.ows.request.get_text(**op_args) gws.write_file('/gws-var/res.xml', text) features = featureinfo.parse(text, crs=ps.request_crs, axis=ps.axis) if features is None: gws.log.debug(f'WMS NOT_PARSED params={params!r}') return [] gws.log.debug(f'WMS FOUND={len(features)} params={params!r}') return [f.transform_to(shape.crs) for f in features]
def props_for(self, user): p = super().props_for(user) if self.table.geometry_column: p = gws.merge(p, geometryType=self.table.geometry_column.gtype) return p
def shape_to_fragment(shape: gws.IShape, view: gws.MapView, style: gws.IStyle = None, label: str = None) -> t.List[gws.XmlElement]: if not shape: return [] geom = t.cast(gws.gis.shape.Shape, shape).geom if geom.is_empty: return [] trans = gws.gis.render.map_view_transformer(view) geom = shapely.ops.transform(trans, geom) if not style: return [_geometry(geom)] sv = style.values with_geometry = sv.with_geometry == 'all' with_label = label and _is_label_visible(view, sv) gt = _geom_type(geom) text = None if with_label: extra_y_offset = 0 if sv.label_offset_y is None: if gt == _TYPE_POINT: extra_y_offset = 12 if gt == _TYPE_LINESTRING: extra_y_offset = 6 text = _label(geom, label, sv, extra_y_offset) marker = None marker_id = None if with_geometry and sv.marker: marker_id = '_M' + gws.random_string(8) marker = _marker(marker_id, sv) icon = None atts: dict = {} if with_geometry and sv.icon: res = _parse_icon(sv.icon, view.dpi) if res: el, w, h = res x, y, w, h = _icon_size_position(geom, sv, w, h) atts = { 'x': f'{int(x)}', 'y': f'{int(y)}', 'width': f'{int(w)}', 'height': f'{int(h)}', } icon = xml2.element(name=el.name, attributes=gws.merge(el.attributes, atts), children=el.children) body = None if with_geometry: _add_paint_atts(atts, sv) if marker: atts['marker-start'] = atts['marker-mid'] = atts[ 'marker-end'] = f'url(#{marker_id})' if gt == _TYPE_POINT or gt == _TYPE_MULTIPOINT: atts['r'] = (sv.point_size or DEFAULT_POINT_SIZE) // 2 if gt == _TYPE_LINESTRING or gt == _TYPE_MUTLILINESTRING: atts['fill'] = 'none' body = _geometry(geom, atts) return gws.compact([marker, body, icon, text])
def find_features( self, args: gws.SearchArgs, source_layers: t.List[gws.SourceLayer]) -> t.List[gws.IFeature]: # first, find features within the bounds of given shapes, # then, filter features precisely # this is more performant than WFS spatial ops (at least for qgis) # and also works without spatial ops support on the provider side bounds = args.bounds search_shape = None if args.shapes: geometry_tolerance = 0.0 if args.tolerance: n, u = args.tolerance geometry_tolerance = n * (args.resolution or 1) if u == 'px' else n search_shape = gws.gis.shape.union( args.shapes).tolerance_polygon(geometry_tolerance) bounds = search_shape.bounds ps = gws.gis.ows.client.prepared_search( inverted_crs=self.inverted_crs, limit=args.limit, bounds=bounds, protocol=self.protocol, protocol_version=self.version, request_crs=self.force_crs, request_crs_format=gws.CrsFormat.EPSG, source_layers=source_layers, ) fmt = self.preferred_formats.get(gws.OwsVerb.GetFeature) if fmt: ps.params.setdefault('OUTPUTFORMAT', fmt) params = gws.merge(ps.params, args.params) text = gws.gis.ows.request.get_text( **self.operation_args(gws.OwsVerb.GetFeature, params=params)) features = featureinfo.parse(text, crs=ps.request_crs, axis=ps.axis) if features is None: gws.log.debug(f'WFS NOT_PARSED params={params!r}') return [] gws.log.debug(f'WFS FOUND={len(features)} params={params!r}') features = [f.transform_to(bounds.crs) for f in features] if not search_shape: return features filtered = [] for f in features: if not f.shape: continue if f.shape.intersects(search_shape): filtered.append(f) if len(filtered) != len(features): gws.log.debug( f'WFS filter: before={len(features)} after={len(filtered)}') return filtered
def props_for(self, user): return gws.merge(super().props_for(user), type='group', layers=self.layers)
def props_for(self, user): p = super().props_for(user) if self.display == 'client': return gws.merge(p, type='xyz', url=self.url) return p
def configure(self): self.version = gws.VERSION self.qgis_version = '' self._devopts = self.var('developer') or {} if self._devopts: gws.log.warn('developer mode enabled') self.set_uid('APP') if self.var('server.qgis.enabled'): qgis_server = gws.import_from_path('gws/plugin/qgis/server.py') self.qgis_version = qgis_server.version() s = f'GWS version {self.version}' if self.qgis_version: s += f', QGis {self.qgis_version}' gws.log.info('*' * 40) gws.log.info(s) gws.log.info('*' * 40) self.locale_uids = self.var('locales') or ['en_CA'] self.monitor = self.require_child(gws.server.monitor.Object, self.var('server.monitor')) self.metadata = gws.lib.metadata.from_config(self.var('metadata')) p = self.var('fonts.dir') if p: gws.lib.font.install_fonts(p) # NB the order of initialization is important # - db # - helpers # - auth providers # - actions, client, web # - finally, projects self.dbs = self.create_children('gws.ext.db.provider', self.var('db.providers')) # helpers are always created, no matter configured or not cnf = {c.get('type'): c for c in self.var('helpers', default=[])} for class_name in self.root.specs.real_class_names('gws.ext.helper'): desc = self.root.specs.object_descriptor(class_name) if desc.ext_type not in cnf: gws.log.debug(f'ad-hoc helper {desc.ext_type!r} will be created') cfg = gws.Config(type=desc.ext_type) cnf[desc.ext_type] = gws.config.parse(self.root.specs, cfg, 'gws.ext.helper.Config') self.helpers = self.create_children('gws.ext.helper', list(cnf.values())) self.auth = self.require_child(gws.base.auth.manager.Object, self.var('auth')) # @TODO default API self.api = self.require_child(gws.base.api.Object, self.var('api')) p = self.var('web.sites') or [gws.base.web.DEFAULT_SITE] ssl = bool(self.var('web.ssl')) cfgs = [gws.merge(c, ssl=ssl) for c in p] self.web_sites = self.create_children(gws.base.web.site.Object, cfgs) self.client = self.create_child_if_config(gws.base.client.Object, self.var('client')) self.projects = [] for cfg in self.var('projects', default=[]): # @TODO: parallel config? self.projects.append(self.create_child(gws.base.project.Object, cfg))
def write_connection(self, user, password): params = gws.merge(self.connect_params, { 'user': user, 'password': password, }) return AlkisConnection(params)
def find_strasse(self, query: types.FindStrasseQuery, **kwargs) -> types.FindStrasseResult: with self.connection() as conn: rs = flurstueck.strasse_list(conn, gws.merge({}, query, kwargs)) return types.FindStrasseResult(strassen=[types.Strasse(r) for r in rs])
def props_for(self, user): return gws.merge( super().props_for(user), layerUids=self.var('layers') or [], pixelTolerance=self.var('pixelTolerance'), )