class BasemapWebMap(Base): __tablename__ = 'basemap_webmap' webmap_id = db.Column(db.ForeignKey(WebMap.id), primary_key=True) resource_id = db.Column(db.ForeignKey(Resource.id), primary_key=True) position = db.Column(db.Integer) display_name = db.Column(db.Unicode, nullable=False) enabled = db.Column(db.Boolean) opacity = db.Column(db.Float) webmap = db.relationship(WebMap, foreign_keys=webmap_id, backref=db.backref( 'basemaps', cascade='all, delete-orphan', order_by=position, collection_class=ordering_list('position'))) resource = db.relationship(Resource, foreign_keys=resource_id, backref=db.backref('_basemaps', cascade='all')) def to_dict(self): return dict(resource_id=self.resource_id, position=self.position, display_name=self.display_name, enabled=self.enabled, opacity=self.opacity)
class MapboxSprite(Base, Resource): identity = 'mapbox_sprite' cls_display_name = _("Sprite") sprite_fileobj_id = db.Column(db.ForeignKey(FileObj.id), nullable=True) sprite_fileobj = db.relationship(FileObj, cascade='all') @classmethod def check_parent(cls, parent): return isinstance(parent, ResourceGroup)
class QgisVectorStyle(Base, Resource): identity = 'qgis_vector_style' cls_display_name = _("QGIS style") implements(IRenderableStyle, ILegendableStyle) __scope__ = DataScope qml_fileobj_id = db.Column(db.ForeignKey(FileObj.id), nullable=True) qml_fileobj = db.relationship(FileObj, cascade='all') @classmethod def check_parent(cls, parent): return IFeatureLayer.providedBy(parent) @property def feature_layer(self): return self.parent @property def srs(self): return self.parent.srs def render_request(self, srs, cond=None): return RenderRequest(self, srs, cond) def _render_image(self, srs, extent, size, cond, padding=0): extended, render_size, target_box = _render_bounds( extent, size, padding) # Выбираем объекты по экстенту feature_query = self.parent.feature_query() # Отфильтровываем объекты по условию if cond is not None: feature_query.filter_by(**cond) # FIXME: Тоже самое, но через интерфейсы if hasattr(feature_query, 'srs'): feature_query.srs(srs) feature_query.intersects(box(*extended, srid=srs.id)) feature_query.geom() features = feature_query() options = VectorRenderOptions( self, features, render_size, extended, target_box) return env.qgis.renderer_job(options) def render_legend(self): options = LegendOptions(self) return env.qgis.renderer_job(options)
class Catalog(Base, Resource): identity = 'catalog' cls_display_name = _("Catalog") __scope__ = CatalogScope root_item_id = db.Column(db.ForeignKey('catalog_item.id'), nullable=False) root_item = db.relationship('CatalogItem', cascade='all') @classmethod def check_parent(cls, parent): return isinstance(parent, ResourceGroup)
class LegendSprite(Base, Resource): identity = 'legend_sprite' cls_display_name = _("Legend") __scope__ = DataScope description_fileobj_id = db.Column(db.Integer, db.ForeignKey(FileObj.id), nullable=True) description_fileobj = db.relationship( FileObj, foreign_keys=[description_fileobj_id], cascade='all') image_fileobj_id = db.Column(db.Integer, db.ForeignKey(FileObj.id), nullable=True) image_fileobj = db.relationship(FileObj, foreign_keys=[image_fileobj_id], cascade='all') @classmethod def check_parent(cls, parent): return IRenderableStyle.providedBy(parent)
class FileBucketFile(Base): __tablename__ = 'file_bucket_file' id = db.Column(db.Integer, primary_key=True) file_bucket_id = db.Column(db.ForeignKey(FileBucket.id), nullable=False) fileobj_id = db.Column(db.ForeignKey(FileObj.id), nullable=False) name = db.Column(db.Unicode(255), nullable=False) mime_type = db.Column(db.Unicode, nullable=False) size = db.Column(db.BigInteger, nullable=False) __table_args__ = (db.UniqueConstraint(file_bucket_id, name), ) fileobj = db.relationship(FileObj, lazy='joined') file_bucket = db.relationship(FileBucket, foreign_keys=file_bucket_id, backref=db.backref( 'files', cascade='all,delete-orphan')) @property def path(self): return env.file_storage.filename(self.fileobj)
class MapboxStyle(Base, Resource): identity = 'mapbox_style' cls_display_name = _('Mapbox style') style = db.Column(db.Unicode, nullable=False) sprite_id = db.Column(db.ForeignKey(MapboxSprite.id, ondelete="SET NULL"), nullable=True) glyphs_id = db.Column(db.ForeignKey(MapboxGlyph.id, ondelete="SET NULL"), nullable=True) sprite = db.relationship(MapboxSprite, foreign_keys=sprite_id, cascade=False, cascade_backrefs=False) glyphs = db.relationship(MapboxGlyph, foreign_keys=glyphs_id, cascade=False, cascade_backrefs=False) @classmethod def check_parent(cls, parent): return isinstance(parent, ResourceGroup)
class MapnikStyle(Base, Resource): identity = 'mapnik_style' cls_display_name = _("Mapnik style") __scope__ = DataScope xml_fileobj_id = db.Column(db.ForeignKey(FileObj.id), nullable=True) xml_fileobj = db.relationship(FileObj, cascade='all') @classmethod def check_parent(cls, parent): return IFeatureLayer.providedBy(parent) @property def feature_layer(self): return self.parent @property def srs(self): return self.parent.srs def render_request(self, srs, cond=None): return RenderRequest(self, srs, cond) def _render_image(self, srs, extent, size, cond, padding=0): """ Рендеринг отдельного изображения(картинки, тайла) :param SRS srs: модель системы координат :param tuple[float] extent: ограничивающий прямоугольник в единицах измерения проекции (метры псевдомеркатора) :param tuple[integer] size: размер изображения (256 * 256) :param dict cond: дополнительное уловие отбора из модели :param float padding: отступ от картинки :return: """ extended, render_size, target_box = _render_bounds(extent, size, padding) with open(env.file_storage.filename(self.xml_fileobj), mode='r') as f: map_xml = f.read() options = ImageOptions(self.id, map_xml, render_size, extended, target_box) return env.mapnik.renderer_job(options) def render_legend(self): with open(env.file_storage.filename(self.xml_fileobj), mode='r') as f: map_xml = f.read() options = LegendOptions(map_xml, self.parent.geometry_type, self.parent.display_name) return env.mapnik.renderer_job(options)
class QgisRasterStyle(Base, Resource): identity = 'qgis_raster_style' cls_display_name = _("QGIS style") __scope__ = DataScope qml_fileobj_id = db.Column(db.ForeignKey(FileObj.id), nullable=True) qml_fileobj = db.relationship(FileObj, cascade='all') @classmethod def check_parent(cls, parent): return parent.cls == 'raster_layer' @property def srs(self): return self.parent.srs def render_request(self, srs): return RenderRequest(self, srs) def _render_image(self, srs, extent, size, cond=None, padding=0): extended, render_size, target_box = _render_bounds( extent, size, padding) # We need raster pyramids so use working directory filename # instead of original filename. gdal_path = env.raster_layer.workdir_filename(self.parent.fileobj) env.qgis.qgis_init() mreq = MapRequest() mreq.set_dpi(96) mreq.set_crs(CRS.from_epsg(srs.id)) style = Style.from_string( _qml_cache(env.file_storage.filename(self.qml_fileobj))) layer = Layer.from_gdal(gdal_path) mreq.add_layer(layer, style) res = mreq.render_image(extent, size) img = qgis_image_to_pil(res) return img
class TrailcamItem(Base, JsonifyMixin): __tablename__ = 'trailcam_items' id = db.Column(db.Integer, primary_key=True, autoincrement=True) email_uid = db.Column(db.Unicode, index=True) name = db.Column(db.Unicode, nullable=False) description = db.Column(db.Unicode) date_received = db.Column(db.DateTime(timezone=True), nullable=False) date_original = db.Column(db.DateTime(timezone=True), nullable=False) message_body = db.Column(db.Unicode) file_name = db.Column(db.Unicode) file_path = db.Column(db.Unicode) file_path_thumbnail = db.Column(db.Unicode) file_size = db.Column(db.BigInteger) trailcam_id = db.Column(db.Integer, db.ForeignKey('trailcam.id')) trailcam = db.relationship('Trailcam', back_populates='items') tags = db.relationship('TrailcamItemTag', secondary=trailcam_items_tags_table, back_populates="items")
class QgisRasterStyle(Base, Resource): identity = 'qgis_raster_style' cls_display_name = _("QGIS style") implements(IRenderableStyle) __scope__ = DataScope qml_fileobj_id = db.Column(db.ForeignKey(FileObj.id), nullable=True) qml_fileobj = db.relationship(FileObj, cascade='all') @classmethod def check_parent(cls, parent): return parent.cls == 'raster_layer' @property def srs(self): return self.parent.srs def render_request(self, srs): return RenderRequest(self, srs) def _render_image(self, srs, extent, size, cond=None, padding=0): extended, render_size, target_box = _render_bounds( extent, size, padding) vsibuf = "/vsimem/%s" % uuid.uuid4() gdal.Translate( vsibuf, env.file_storage.filename(self.parent.fileobj), projWin=[extent[0], extent[3], extent[2], extent[1]], width=size[0], height=size[1], ) options = RasterRenderOptions( self, vsibuf, render_size, extended, target_box) return env.qgis.renderer_job(options)
class Trailcam(Base, Resource): identity = 'trailcam' cls_display_name = _('Trail camera') __scope__ = DataScope lat = db.Column(db.Float) lon = db.Column(db.Float) is_auto = db.Column(db.Boolean) filter = db.Column(db.Unicode) email_connection_id = db.Column(db.ForeignKey(Resource.id), nullable=False) email_connection = db.relationship(Resource, foreign_keys=email_connection_id, cascade=False, cascade_backrefs=False) items = db.relationship('TrailcamItem', back_populates='trailcam') @classmethod def check_parent(cls, parent): return isinstance(parent, TrailcamGroup)
class CatalogItem(Base): __tablename__ = 'catalog_item' id = db.Column(db.Integer, primary_key=True) parent_id = db.Column(db.Integer, db.ForeignKey('catalog_item.id')) item_type = db.Column(db.Enum('root', 'group', 'layer'), nullable=False) position = db.Column(db.Integer) display_name = db.Column(db.Unicode) description = db.Column(db.Unicode) layer_enabled = db.Column(db.Boolean) layer_wms_id = db.Column(db.ForeignKey(Resource.id)) layer_wfs_id = db.Column(db.ForeignKey(Resource.id)) layer_webmap_id = db.Column(db.ForeignKey(Resource.id)) layer_resource_id = db.Column(db.ForeignKey(Resource.id), nullable=True) parent = db.relationship( 'CatalogItem', remote_side=id, backref=db.backref( 'children', order_by=position, cascade='all, delete-orphan', collection_class=ordering_list('position'))) def to_dict(self): if self.item_type in ('root', 'group'): children = list(self.children) sorted(children, key=lambda c: c.position) if self.item_type == 'root': return dict( item_type=self.item_type, children=[i.to_dict() for i in children], ) elif self.item_type == 'group': return dict( item_type=self.item_type, display_name=self.display_name, description=self.description, children=[i.to_dict() for i in children], ) elif self.item_type == 'layer': return dict( item_type=self.item_type, display_name=self.display_name, description=self.description, layer_enabled=self.layer_enabled, layer_webmap_id=self.layer_webmap_id, layer_wms_id=self.layer_wms_id, layer_wfs_id=self.layer_wfs_id, layer_resource_id=self.layer_resource_id ) def from_dict(self, data): assert data['item_type'] == self.item_type if data['item_type'] in ('root', 'group') and 'children' in data: self.children = [] for i in data['children']: child = CatalogItem(parent=self, item_type=i['item_type']) child.from_dict(i) self.children.append(child) for a in ('display_name', 'description', 'layer_enabled', 'layer_webmap_id', 'layer_wms_id', 'layer_wfs_id', 'layer_resource_id'): if a in data: setattr(self, a, data[a])
class TrailcamGroup(Base, Resource): identity = 'trailcam_group' cls_display_name = _('Trail cameras') __scope__ = DataScope @classmethod def check_parent(cls, parent): return (parent is None) or isinstance(parent, ResourceGroup) trailcam_items_tags_table = db.Table( 'trailcam_items_tags', Base.metadata, db.Column('trailcam_id', db.Integer, db.ForeignKey('trailcam_items.id')), db.Column('tag_id', db.Integer, db.ForeignKey('trailcam_item_tag.id'))) class TrailcamItemTag(Base): __tablename__ = 'trailcam_item_tag' id = db.Column(db.Integer, primary_key=True, autoincrement=True) name = db.Column(db.Unicode, nullable=False, unique=True) description = db.Column(db.Unicode) items = db.relationship('TrailcamItem', secondary=trailcam_items_tags_table, back_populates='tags')
class QgisVectorStyle(Base, Resource): identity = 'qgis_vector_style' cls_display_name = _("QGIS style") implements(IRenderableStyle) __scope__ = DataScope qml_fileobj_id = db.Column(db.ForeignKey(FileObj.id), nullable=True) qml_fileobj = db.relationship(FileObj, cascade='all') @classmethod def check_parent(cls, parent): return IFeatureLayer.providedBy(parent) @property def feature_layer(self): return self.parent @property def srs(self): return self.parent.srs def render_request(self, srs): return RenderRequest(self, srs) def _render_image(self, srs, extent, size, padding=0): res_x = (extent[2] - extent[0]) / size[0] res_y = (extent[3] - extent[1]) / size[1] # Экстент с учетом отступов extended = ( max(srs.minx, extent[0] - res_x * padding), max(srs.miny, extent[1] - res_y * padding), min(srs.maxx, extent[2] + res_x * padding), min(srs.maxy, extent[3] + res_y * padding), ) # Маска отступов pmask = (extended[0] != srs.minx, extended[1] != srs.miny, extended[2] != srs.maxx, extended[3] != srs.maxy) # Размер изображения с учетом отступов render_size = (size[0] + int(pmask[0] + pmask[2]) * padding, size[1] + int(pmask[1] + pmask[3]) * padding) # Фрагмент изображения размера size target_box = (pmask[0] * padding, pmask[3] * padding, size[0] + pmask[0] * padding, size[1] + pmask[3] * padding) # Выбираем объекты по экстенту feature_query = self.parent.feature_query() # FIXME: Тоже самое, но через интерфейсы if hasattr(feature_query, 'srs'): feature_query.srs(srs) feature_query.intersects(box(*extended, srid=srs.id)) feature_query.geom() features = feature_query() res_img = None try: dirname, fndata, fnstyle = None, None, None dirname = mkdtemp() fndata = os.path.join(dirname, 'layer.geojson') with open(fndata, 'wb') as fd: fd.write(geojson.dumps(features)) fnstyle = os.path.join(dirname, 'layer.qml') os.symlink(env.file_storage.filename(self.qml_fileobj), fnstyle) result = Queue() env.qgis.queue.put( (fndata, self.srs, render_size, extended, target_box, result)) render_timeout = int(env.qgis.settings.get('render_timeout')) res_img = result.get(block=True, timeout=render_timeout) finally: if fndata and os.path.isfile(fndata): os.unlink(fndata) if fnstyle and os.path.isfile(fnstyle): os.unlink(fnstyle) if dirname and os.path.isdir(dirname): os.rmdir(dirname) return res_img
class QgisVectorStyle(Base, Resource): identity = 'qgis_vector_style' cls_display_name = _("QGIS style") __scope__ = (DataScope, DataStructureScope) qml_fileobj_id = db.Column(db.ForeignKey(FileObj.id), nullable=True) svg_marker_library_id = db.Column(db.ForeignKey(SVGMarkerLibrary.id), nullable=True) qml_fileobj = db.relationship(FileObj, cascade='all') svg_marker_library = db.relationship( SVGMarkerLibrary, foreign_keys=svg_marker_library_id, cascade=False, cascade_backrefs=False, ) @classmethod def check_parent(cls, parent): return IFeatureLayer.providedBy(parent) @property def feature_layer(self): return self.parent @property def srs(self): return self.parent.srs def render_request(self, srs, cond=None): return RenderRequest(self, srs, cond) def _render_image(self, srs, extent, size, cond, padding=0): extended, render_size, target_box = _render_bounds( extent, size, padding) feature_query = self.parent.feature_query() # Apply filter condition if cond is not None: feature_query.filter_by(**cond) # TODO: Do this via interfaces if hasattr(feature_query, 'srs'): feature_query.srs(srs) feature_query.intersects(box(*extended, srid=srs.id)) feature_query.geom() env.qgis.qgis_init() crs = CRS.from_epsg(srs.id) mreq = MapRequest() mreq.set_dpi(96) mreq.set_crs(crs) def path_resolver(name): for skip_path in SKIP_PATHS: if name.startswith(skip_path): name = name.replace(skip_path, '', 1) break if path.isabs(name): raise ValueError("Absolute paths are not allowed.") name = path.normpath(name) if name[-4:].lower() == '.svg': name = name[:-4] items = name.split(path.sep) for i in range(len(items)): candidate = path.sep.join(items[i:]) filename = env.svg_marker_library.lookup( candidate, self.svg_marker_library) if filename is not None: return filename return name style = Style.from_string( _qml_cache(env.file_storage.filename(self.qml_fileobj)), path_resolver) style_attrs = style.used_attributes() qhl_fields = list() cnv_fields = list() qry_fields = list() for field in self.parent.fields: fkeyname = field.keyname if (style_attrs is not None) and (fkeyname not in style_attrs): continue field_to_qgis = _FIELD_TYPE_TO_QGIS[field.datatype] qhl_fields.append((fkeyname, field_to_qgis[0])) cnv_fields.append((fkeyname, field_to_qgis[1])) qry_fields.append(fkeyname) feature_query.fields(*qry_fields) features = list() for feat in feature_query(): features.append((feat.id, feat.geom.wkb, tuple([ convert(feat.fields[field]) for field, convert in cnv_fields ]))) if len(features) == 0: return None layer = Layer.from_data(_GEOM_TYPE_TO_QGIS[self.parent.geometry_type], crs, tuple(qhl_fields), tuple(features)) mreq.add_layer(layer, style) res = mreq.render_image(extent, size) return qgis_image_to_pil(res) def render_legend(self): env.qgis.qgis_init() mreq = MapRequest() mreq.set_dpi(96) style = Style.from_string( _qml_cache(env.file_storage.filename(self.qml_fileobj))) layer = Layer.from_data(_GEOM_TYPE_TO_QGIS[self.parent.geometry_type], CRS.from_epsg(self.parent.srs.id), (), ()) mreq.add_layer(layer, style) res = mreq.render_legend() img = qgis_image_to_pil(res) # PNG-compressed buffer is required for render_legend() # TODO: Switch to PIL Image in future! buf = BytesIO() img.save(buf, 'png') buf.seek(0) return buf