Browse Source

2 new models CoveredArea and RegisteredOffice that hold Geometry columns

Gu1 11 years ago
parent
commit
1feee6097e
3 changed files with 113 additions and 2 deletions
  1. 14 2
      ffdnispdb/crawler.py
  2. 71 0
      ffdnispdb/models.py
  3. 28 0
      ffdnispdb/utils.py

+ 14 - 2
ffdnispdb/crawler.py

@@ -10,7 +10,8 @@ import requests
 
 from ispformat.validator import validate_isp
 from .models import ISP
-from . import app
+from .utils import dict_to_geojson
+from . import app, db
 
 
 def get_encoding(content_type):
@@ -68,7 +69,7 @@ class Crawler(object):
         for e in errs:
             r.append(u'    %s: %s'%('.'.join(list(e.schema_path)[1:]), e.message))
 
-        return u'\n'.join(r)
+        return u'\n'.join(r)+'\n'
 
     def pre_done_cb(self, *args):
         pass
@@ -283,6 +284,17 @@ class Crawler(object):
         else:
             yield self.info('Done. No errors encountered \o')
 
+        for ca in jdict.get('coveredAreas', []):
+            if not 'area' in ca:
+                continue
+            gjson=dict_to_geojson(ca['area'])
+            is_valid=bool(db.session.query(db.func.GeomFromGeoJSON(gjson) != None).first()[0])
+            if not is_valid:
+                yield self.err('GeoJSON data for covered area "%s" cannot '
+                               'be handled by our database'%esc(ca['name']))
+                yield self.abort('Please fix your GeoJSON')
+                return
+
         ret=self.pre_done_cb(jdict)
         if ret:
             yield ret

+ 71 - 0
ffdnispdb/models.py

@@ -6,10 +6,12 @@ import os
 import itertools
 from datetime import datetime
 from . import db, app
+from .utils import dict_to_geojson
 import flask_sqlalchemy
 from sqlalchemy.types import TypeDecorator, VARCHAR
 from sqlalchemy.ext.mutable import MutableDict
 from sqlalchemy import event
+import geoalchemy as geo
 import whoosh
 from whoosh import fields, index, qparser
 
@@ -44,6 +46,7 @@ class JSONEncodedDict(TypeDecorator):
 
 
 class ISP(db.Model):
+    __tablename__ = 'isp'
     id = db.Column(db.Integer, primary_key=True)
     name = db.Column(db.String, nullable=False, index=True, unique=True)
     shortname = db.Column(db.String(12), index=True, unique=True)
@@ -57,6 +60,9 @@ class ISP(db.Model):
     tech_email = db.Column(db.String)
     cache_info = db.Column(MutableDict.as_mutable(JSONEncodedDict))
     json = db.Column(MutableDict.as_mutable(JSONEncodedDict))
+    covered_areas = db.relationship('CoveredArea', backref='isp')
+#    covered_areas_query = db.relationship('CoveredArea', lazy='dynamic')
+    registered_office = db.relationship('RegisteredOffice', uselist=False, backref='isp')
 
     def __init__(self, *args, **kwargs):
         super(ISP, self).__init__(*args, **kwargs)
@@ -69,6 +75,34 @@ class ISP(db.Model):
         if 'shortname' in self.json:
             assert self.shortname == self.json['shortname']
 
+        if db.inspect(self).attrs.json.history.has_changes():
+            self._sync_covered_areas()
+
+    def _sync_covered_areas(self):
+        """
+        Called to synchronise between json['coveredAreas'] and the
+        covered_areas table, when json was modified.
+        """
+        # delete current covered areas
+        self.covered_areas.delete()
+        RegisteredOffice.query.filter_by(isp_id=self.id).delete()
+
+        for ca_js in self.json.get('coveredAreas', []):
+            ca=CoveredArea()
+            ca.name=ca_js['name']
+            area=ca_js.get('area')
+            ca.area=db.func.CastToMultiPolygon(
+                db.func.GeomFromGeoJSON(dict_to_geojson(area))
+            ) if area else None
+            self.covered_areas.append(ca)
+
+        coords=self.json.get('coordinates')
+        if coords:
+            self.registered_office=RegisteredOffice(
+                point=db.func.MakePoint(coords['longitude'], coords['latitude'], 4326)
+            )
+
+
     def covered_areas_names(self):
         return [c['name'] for c in self.json.get('coveredAreas', [])]
 
@@ -102,6 +136,43 @@ class ISP(db.Model):
         return u'<ISP %r>' % (self.shortname if self.shortname else self.name,)
 
 
+class CoveredArea(db.Model):
+    __tablename__ = 'covered_areas'
+    id = db.Column(db.Integer, primary_key=True)
+    isp_id = db.Column(db.Integer, db.ForeignKey('isp.id'))
+    name = db.Column(db.String)
+    area = geo.GeometryColumn(geo.MultiPolygon(2))
+    area_geojson = db.column_property(db.func.AsGeoJSON(db.literal_column('area')), deferred=True)
+
+    @classmethod
+    def containing(cls, coords):
+        """
+        Return CoveredAreas containing point (lat,lon)
+        """
+        return cls.query.filter(
+            cls.area != None,
+            cls.area.gcontains(db.func.MakePoint(coords[1], coords[0])) == 1
+        )
+
+    def __repr__(self):
+        return u'<CoveredArea %r>' % (self.name,)
+
+geo.GeometryDDL(CoveredArea.__table__)
+
+
+class RegisteredOffice(db.Model):
+    __tablename__ = 'registered_offices'
+    id = db.Column(db.Integer, primary_key=True)
+    isp_id = db.Column(db.Integer, db.ForeignKey('isp.id'))
+    point = geo.GeometryColumn(geo.Point(0))
+
+geo.GeometryDDL(RegisteredOffice.__table__)
+
+@event.listens_for(db.metadata, 'before_create')
+def init_spatialite_metadata(target, conn, **kwargs):
+    conn.execute('SELECT InitSpatialMetaData(1)')
+
+
 def pre_save_hook(sess):
     for v in itertools.chain(sess.new, sess.dirty):
         if hasattr(v, 'pre_save') and hasattr(v.pre_save, '__call__'):

+ 28 - 0
ffdnispdb/utils.py

@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from collections import OrderedDict
+import json
+
+
+
+def dict_to_geojson(d_in):
+    """
+    Encode a dict representing a GeoJSON object into a JSON string.
+    This is needed because spatialite's GeoJSON parser is not really
+    JSON-compliant and it fails when keys are not in the right order.
+    """
+    d=OrderedDict()
+    d['type']=d_in['type']
+
+    if 'crs' in d_in:
+        d['crs']=d_in['crs']
+    # our spatialite geo column is defined with EPSG SRID 4326 (WGS 84)
+    d['crs'] = {'type': 'name', 'properties': {'name': 'urn:ogc:def:crs:EPSG:4326'}}
+
+    if 'bbox' in d_in:
+        d['bbox']=d_in['bbox']
+
+    d['coordinates']=d_in['coordinates']
+
+    return json.dumps(d)
+