Skip to content

Commit 969f873

Browse files
committed
Add support city plan boundaries
1 parent 7e22997 commit 969f873

File tree

7 files changed

+140
-8
lines changed

7 files changed

+140
-8
lines changed

Diff for: demo/static/js/address.coffee

+24
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,27 @@ $("#district-input").on 'change', ->
8585
borders.bindPopup match_obj.name
8686
borders.addTo map
8787
map.fitBounds borders.getBounds()
88+
89+
show_plans = false
90+
$("#show-plans").on 'click', ->
91+
show_plans = true
92+
refresh_plans()
93+
94+
map.on 'moveend', (ev) ->
95+
if show_plans
96+
refresh_plans()
97+
98+
plans = {}
99+
100+
refresh_plans = ->
101+
bounds = map.getBounds().toBBoxString()
102+
$.getJSON API_PREFIX + 'v1/plan/', {bbox: bounds, limit: 100}, (data) ->
103+
for obj in data.objects
104+
if obj.id of plans
105+
continue
106+
plans[obj.id] = obj
107+
geom = L.geoJson obj.geometry,
108+
style:
109+
weight: 2
110+
geom.bindPopup "Kaava nr. <b>#{obj.origin_id}</b>"
111+
geom.addTo map

Diff for: demo/templates/demo.html

+4-1
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,13 @@
3939
<h1>Paikkatieto-demo</h1>
4040
</div>
4141
<div style="clear: both"></div>
42-
<div class="span8 row" style="margin-bottom: 20px">
42+
<div class="span8 row">
4343
<input type="text" class="span4" id="address-input" placeholder="Input an address...">
4444
<input type="text" class="span4" id="district-input" placeholder="Input a district...">
4545
</div>
46+
<div class="span8 row" style="margin-bottom: 20px">
47+
<button id="show-plans" class="btn">Show plans</button>
48+
</div>
4649
<div style="clear: both"></div>
4750
<div class="well span8 row">
4851
<div id="map" style="margin-left: 10px; width: 600px; height: 400px"></div>

Diff for: geo/api.py

+40-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import re
33

44
from django.db.models import Q
5-
from django.contrib.gis.geos import Point
5+
from django.contrib.gis.geos import Point, Polygon
66
from django.contrib.gis.measure import D
77
from tastypie.resources import ModelResource
88
from tastypie.exceptions import InvalidFilterError
@@ -189,7 +189,45 @@ class Meta:
189189
'name': ALL,
190190
}
191191

192+
def build_bbox_filter(bbox_val, field_name):
193+
points = bbox_val.split(',')
194+
if len(points) != 4:
195+
raise InvalidFilterError("bbox must be in format 'left,bottom,right,top'")
196+
try:
197+
points = [float(p) for p in points]
198+
except ValueError:
199+
raise InvalidFilterError("bbox values must be floating point")
200+
poly = Polygon.from_bbox(points)
201+
poly.srid = 4326
202+
poly.transform(PROJECTION_SRID)
203+
return {"%s__intersects" % field_name: poly}
204+
205+
class PlanResource(GeometryModelResource):
206+
municipality = fields.ToOneField(MunicipalityResource, 'municipality',
207+
help_text="ID of the municipality that this plan belongs to")
208+
209+
def build_filters(self, filters=None):
210+
orm_filters = super(PlanResource, self).build_filters(filters)
211+
if filters and 'bbox' in filters:
212+
bbox_filter = build_bbox_filter(filters['bbox'], 'geometry')
213+
orm_filters.update(bbox_filter)
214+
return orm_filters
215+
216+
def full_dehydrate(self, bundle, for_list=False):
217+
# Convert to WGS-84 before outputting.
218+
bundle.obj.geometry.transform(4326)
219+
return super(PlanResource, self).full_dehydrate(bundle, for_list)
220+
221+
class Meta:
222+
queryset = Plan.objects.all()
223+
filtering = {
224+
'municipality': ALL,
225+
'origin_id': ['exact'],
226+
'in_effect': ['exact'],
227+
'geometry': ALL,
228+
}
229+
192230
all_resources = [
193231
MunicipalityResource, MunicipalityBoundaryResource, AddressResource, POICategoryResource,
194-
POIResource, DistrictResource
232+
POIResource, DistrictResource, PlanResource,
195233
]

Diff for: geo/importer/helsinki.py

+52-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.conf import settings
77
from django import db
88
from django.contrib.gis.gdal import DataSource, SpatialReference, CoordTransform
9-
from django.contrib.gis.geos import GEOSGeometry, MultiPolygon, Point
9+
from django.contrib.gis.geos import GEOSGeometry, MultiPolygon, Polygon, Point
1010

1111
MUNI_URL = "http://tilastokeskus.fi/meta/luokitukset/kunta/001-2013/tekstitiedosto.txt"
1212

@@ -52,7 +52,7 @@ def import_districts(self):
5252
obj_map[obj.origin_id] = obj
5353
obj.found = False
5454

55-
def import_from_file(fname):
55+
def import_from_file(fname, district_type):
5656
path = os.path.join(self.data_path, 'aluejaot', fname)
5757
ds = DataSource(path, encoding='iso8859-1')
5858
lyr = ds[0]
@@ -76,15 +76,63 @@ def import_from_file(fname):
7676
district.name = name
7777
print district.name
7878
district.borders = GEOSGeometry(geom.wkb, srid=geom.srid)
79+
district.type = district_type
7980
district.save()
8081
district.found = True
81-
import_from_file('osaalue.tab')
82-
import_from_file('kaupunginosa.tab')
82+
import_from_file('osaalue.tab', 'osa-alue')
83+
import_from_file('kaupunginosa.tab', 'kaupunginosa')
8384

8485
for key, obj in obj_map.iteritems():
8586
if not obj.found:
8687
print "District %s deleted" % obj.name
8788

89+
def _import_plans(self, fname, in_effect):
90+
path = os.path.join(self.data_path, 'kaavahakemisto', fname)
91+
ds = DataSource(path, encoding='iso8859-1')
92+
lyr = ds[0]
93+
94+
for idx, feat in enumerate(lyr):
95+
origin_id = feat['kaavatunnus'].as_string()
96+
geom = feat.geom
97+
geom.srid = GK25_SRID
98+
geom.transform(PROJECTION_SRID)
99+
if origin_id not in self.plan_map:
100+
obj = Plan(origin_id=origin_id, municipality=self.muni)
101+
self.plan_map[origin_id] = obj
102+
else:
103+
obj = self.plan_map[origin_id]
104+
if not obj.found:
105+
obj.geometry = None
106+
poly = GEOSGeometry(geom.wkb, srid=geom.srid)
107+
if obj.geometry:
108+
obj.geometry.append(poly)
109+
else:
110+
obj.geometry = MultiPolygon(poly)
111+
obj.in_effect = in_effect
112+
obj.found = True
113+
if (idx % 500) == 0:
114+
print "%d imported" % idx
115+
if in_effect:
116+
type_str = "in effect"
117+
else:
118+
type_str = "development"
119+
print "%d %s plans imported" % (idx, type_str)
120+
121+
def import_plans(self):
122+
self.plan_map = {}
123+
self.muni = Municipality.objects.get(name="Helsinki")
124+
for obj in Plan.objects.filter(municipality=self.muni):
125+
self.plan_map[obj.origin_id] = obj
126+
obj.found = False
127+
self._import_plans('Lv_rajaus.TAB', True)
128+
self._import_plans('Kaava_vir_rajaus.TAB', False)
129+
print "Saving"
130+
for key, obj in self.plan_map.iteritems():
131+
if obj.found:
132+
obj.save()
133+
else:
134+
print "Plan %s deleted" % obj.name
135+
88136
def import_addresses(self):
89137
f = open(os.path.join(self.data_path, 'pks_osoite.csv'))
90138
reader = csv.reader(f, delimiter=',')

Diff for: geo/management/commands/geo_import.py

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class Command(BaseCommand):
1010
option_list = BaseCommand.option_list + (
1111
make_option('--municipality', action='store_true', dest='municipality', help='Import municipalities'),
1212
make_option('--district', action='store_true', dest='district', help='Import muni districts'),
13+
make_option('--plan', action='store_true', dest='plan', help='Import muni plan'),
1314
make_option('--address', action='store_true', dest='address', help='Import addresses'),
1415
make_option('--poi', action='store_true', dest='poi', help='Import POIs'),
1516
make_option('--all', action='store_true', dest='all', help='Import all entities.'),
@@ -27,6 +28,9 @@ def handle(self, *args, **options):
2728
if options['all'] or options['district']:
2829
print "Importing districts"
2930
importer.import_districts()
31+
if options['all'] or options['plan']:
32+
print "Importing plans"
33+
importer.import_plans()
3034
if options['all'] or options['address']:
3135
print "Importing addresses"
3236
importer.import_addresses()

Diff for: geo/models.py

+15
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,26 @@ class MunicipalityBoundary(models.Model):
2424
class District(models.Model):
2525
municipality = models.ForeignKey(Municipality)
2626
name = models.CharField(max_length=50)
27+
type = models.CharField(max_length=20)
2728
borders = models.PolygonField()
2829
origin_id = models.CharField(max_length=20)
2930

3031
objects = models.GeoManager()
3132

33+
class Meta:
34+
unique_together = (('municipality', 'origin_id'))
35+
36+
class Plan(models.Model):
37+
municipality = models.ForeignKey(Municipality)
38+
geometry = models.MultiPolygonField(srid=PROJECTION_SRID)
39+
origin_id = models.CharField(max_length=20)
40+
in_effect = models.BooleanField()
41+
42+
objects = models.GeoManager()
43+
44+
class Meta:
45+
unique_together = (('municipality', 'origin_id'),)
46+
3247
class Address(models.Model):
3348
street = models.CharField(max_length=50, db_index=True,
3449
help_text="Name of the street")

Diff for: requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Django
2-
-e git+https://github.com/toastdriven/django-tastypie.git#egg=django-tastypie
2+
django-tastypie
33
django_compressor
44
psycopg2
55
requests

0 commit comments

Comments
 (0)