【问题标题】:AdminModelConvertor for Geometry Field (LON/LAT)几何字段 (LON/LAT) 的 AdminModelConvertor
【发布时间】:2014-05-02 08:57:33
【问题描述】:

我想为 Flask-Admin 创建一个视图,以便在几何字段中输入坐标。如何创建两个文本字段并将它们转换为几何对象?

这是我迄今为止尝试过的(除了不可数的其他东西)

class CustomAdminConverter(AdminModelConverter):
    @converts('geoalchemy2.types.Geometry')
    def convert_geometry(self, field_args, **extra):
        return WayToCoordinatesField(**field_args)

class WayToCoordinatesField(wtf.TextAreaField):
    def process_data(self, value):
        print "run" #is never called??
        if value is None:
            value = {}
        else:
            value = "test"
        return value

class POIView(ModelView):
    inline_model_form_converter = MyInlineModelConverter
    model_form_converter=CustomAdminConverter
    can_create = True
    def __init__(self, session, **kwargs):
        # You can pass name and other parameters if you want to
        super(POIView, self).__init__(POI, session, **kwargs)

    def scaffold_form(self):
        form_class = super(POIView, self).scaffold_form()
        form_class.way = wtf.TextAreaField("Coordinates")
        return form_class

POI 对象如下所示:

class POI(db.Model):
    __tablename__ = 'zo_poi'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text())
    tags = db.Column(HSTORE())
    src = db.Column(db.Text())
    way = db.Column(Geometry('POINT'))
    intern = db.Column(db.BOOLEAN())

非常感谢您的帮助!

【问题讨论】:

    标签: python sqlalchemy flask flask-admin geoalchemy


    【解决方案1】:

    通过交互式地图找到了解决方案。 这是我所做的:

    管理员/fields.py:

    import json
    from wtforms import Field
    import geojson
    from shapely.geometry import asShape
    from geoalchemy2.shape import to_shape, from_shape
    from wtforms.widgets import html_params, HTMLString
    from geoalchemy2.elements import WKTElement, WKBElement
    from flask import render_template
    class WTFormsMapInput(object):
        def __call__(self, field, **kwargs):
            options = dict(name=field.name, value=field.data, height=field.height, width=field.width,
                           geometry_type=field.geometry_type)
    
            return HTMLString(render_template("admin/admin_map.html", height=options['height'], width=options['width'],
                                              geolayer=self.geolayer(field.data), preview=False))
    
        def geolayer(self, value):
            if value is not None:
                html = ""
                subme = """var geojson = JSON.parse('%s');
                           editableLayers.addData(geojson);
                           update()
                           map.fitBounds(editableLayers.getBounds());"""
                # If validation in Flask-Admin fails on somethign other than
                # the spatial column, it is never converted to geojson.  Didn't
                # spend the time to figure out why, so I just convert here.
                if isinstance(value, (WKTElement, WKBElement)):
                    html += subme % geojson.dumps(to_shape(value))
                else:
                    html += subme % geojson.dumps(value)
                return html
    
    
    class WTFormsMapField(Field):
        widget = WTFormsMapInput()
    
        def __init__(self, label='', validators=None, geometry_type=None, width=500, height=500,
                     **kwargs):
            super(WTFormsMapField, self).__init__(label, validators, **kwargs)
            self.width = width
            self.height = height
            self.geometry_type = geometry_type
    
        def _value(self):
            """ Called by widget to get GeoJSON representation of object """
            if self.data:
                return self.data
            else:
                return json.loads(json.dumps(dict()))
    
        def process_formdata(self, valuelist):
            """ Convert GeoJSON to DB object """
            if valuelist:
                geo_ob = geojson.loads(valuelist[0])
                self.data = from_shape(asShape(geo_ob.geometry))
            else:
                self.data = None
    
        def process_data(self, value):
            """ Convert DB object to GeoJSON """
            if value is not None:
                self.data = geojson.loads(geojson.dumps(to_shape(value)))
                print self.data
            else:
                self.data = None
    

    templates/admin/admin_map.html

    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css"/>
    <link rel="stylesheet" href="http://leaflet.github.io/Leaflet.draw/leaflet.draw.css"/>
    <script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
    <script src="http://leaflet.github.io/Leaflet.draw/leaflet.draw.js"></script>
    <script src="/admin/static/vendor/jquery-1.8.3.min.js" type="text/javascript"></script>
    <script src="/static/js/googleOverlay/layer/tile/Google.js"></script>
    <script src="http://maps.google.com/maps/api/js?v=3&sensor=false"></script>
    
    <div id="map" style="height: {{ height }}px; width: {{ width }}px;"></div>
    <input id="geojson" type="text" name="{{ name }}"/>
    
    <script>
        var map = new L.Map('map', {
                    center: new L.LatLng(47.3682, 8.879),
                    zoom: 11
                    {%  if preview %}
                    ,
                        dragging: false,
                        touchzoom: false,
                        scrollWheelZoom: false,
                        doubleClickZoom: false,
                        boxZoom: false,
                        tap: false,
                        keyboard: false,
                        zoomControl: false
    
                    {% endif %}
                }
        );
        var ggl = new L.Google('ROADMAP');
        map.addLayer(ggl);
        var osm = new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png');
        map.addControl(new L.Control.Layers({'OpenStreetMap': osm, 'Google Maps': ggl}, {}));
    
        var editableLayers = L.geoJson().addTo(map);
    
        {{ geolayer |safe }}
        {% if not preview %}
        var drawControl = new L.Control.Draw({
            position: 'topright',
            draw: {
                polyline: false,
                circle: false,
                rectangle: false,
                polygon: true,
                marker: true,
            },
            edit: {
                featureGroup: editableLayers
            }
        });
        {% endif %}
        map.addControl(drawControl);
    
        map.on('draw:created', function (e) {
            editableLayers.addLayer(e.layer);
            update();
        });
    
        map.on('draw:edited', function (e) {
            // Just use the first layer
            update();
        })
    
        map.on('draw:deleted', function (e) {
            update();
        })
    
        function update() {
            if (editableLayers.getLayers().length > 0) {
                $("#geojson").val(JSON.stringify(editableLayers.getLayers()[0].toGeoJSON()));
            } else {
                $("#geojson").val(null);
            }
        }
    
    </script>
    

    admin/views.py

    class POIView(ModelView):
        can_create = True
        form_overrides = dict(location=WTFormsMapField)
        form_args = dict(
            way=dict(
                geometry_type='Polygon', height=500, width=500
            )
        )
        column_formatters = dict(tags=lambda v, c, m, p: (u', '.join(u"=".join([k, v]) for k, v in m.tags.items())),
                                 )
    
        def __init__(self, session, **kwargs):
            super(POIView, self).__init__(POI, session, **kwargs)
    
        def scaffold_form(self):
            form_class = super(POIView, self).scaffold_form()
            form_class.way = WTFormsMapField()
            form_class.tags = MySelect2TagsField("Tags",None)
            return form_class
    

    admin/models.py

    class POI(db.Model):
        __tablename__ = 'zo_poi'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.Text())
        tags = db.Column(HSTORE())
        src = db.Column(db.Text())
        way = db.Column(Geometry('point', srid=4326))
        intern = db.Column(db.BOOLEAN())
    

    【讨论】:

      【解决方案2】:

      从 Flask-Admin 1.0.9 版开始,它现在支持 Geoalchemy2 Geometry 列(以及 1.1.0 添加的 Geography 列)。

      最大的变化是从flask-admin.contrib.geoa 导入ModelView 而不是flask-admin.contrib.sqla,所以一个简单的模型看起来像:

      from geoalchemy2 import Geometry
      from flask-admin.contrib.geoa import ModelView
      
      app.config['MAPBOX_MAP_ID'] = 'example.abc123'    
      
      class Location(db.Model):
          id = db.Column(db.Integer, primary_key=True)
          name = db.Column(db.String(64), unique=True)
          point = db.Column(Geometry("Point", 4326))
      
      admin = Admin(app)
      admin.add_view(ModelView(Location, db.session))
      

      在列表视图中,每个都会显示一个小的预览图,然后在编辑或创建视图中显示一个leaflet.draw 视图。

      对于线条或复杂的多边形,您可能希望用form_widget_args 覆盖ModelView 以获得更合理大小的编辑空间。

      class Polygon(db.Model):
          ...
          polygon = db.Column(Geometry("Polygon", 4326)
      
      class PolygonView(ModelView):
          form_widget_args = {'polgon': {'data-height': 400; 'data-width': 400}}
      
      admin.add_view(PolygonView(Polygon, db.session))
      

      【讨论】:

      • 哇.. 好的,当我再次从事该项目时,我会明确地检查这一点:) thx。
      • 在当前版本的 Flask-Admin(1.1.0) 中,自从 Mapbox API 升级到 v4 后,对 Geometry 列的支持被破坏了。 github.com/mrjoes/flask-admin/blob/…
      • @blurrcat 新创建的地图不支持 v3 API,但尚未关闭对 2015 年 3 月 15 日之前创建的地图的支持。获取 v4 API 在我的待办事项列表中支持工作。
      猜你喜欢
      • 2018-09-05
      • 1970-01-01
      • 2012-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多