13
from booleano.parser import Grammar, EvaluableParseManager, SymbolTable, Bind
14
from booleano.operations import Variable
18
def __init__(self, name, language):
21
self.language = language
26
def addPath(self, path, code, name):
27
self.paths[code] = {"path": path, "name": name}
30
map = {"paths": self.paths, "width": self.width, "height": self.height, "insets": self.insets, "projection": self.projection}
31
return "jQuery.fn.vectorMap('addMap', '"+self.name+"_"+self.projection['type']+"',"+json.dumps(map)+');'
35
def __init__(self, config):
37
'buffer_distance': -0.4,
38
'simplify_tolerance': 0.2,
53
self.map = Map(args['name'], args.get('language'))
55
if args.get('sources'):
56
self.sources = args['sources']
59
'input_file': args.get('input_file'),
60
'where': args.get('where'),
61
'name_field': args.get('name_field'),
62
'code_field': args.get('code_field'),
63
'input_file_encoding': args.get('input_file_encoding')
70
'input_file_encoding': 'iso-8859-1'
73
for index in range(len(self.sources)):
74
for key in default_source:
75
if self.sources[index].get(key) is None:
76
self.sources[index][key] = default_source[key]
79
self.width = args.get('width')
80
self.left = args.get('left')
81
self.top = args.get('top')
82
self.minimal_area = args.get('minimal_area')
83
self.longitude0 = float(args.get('longitude0'))
84
self.projection = args.get('projection')
85
self.precision = args.get('precision')
86
self.buffer_distance = args.get('buffer_distance')
87
self.simplify_tolerance = args.get('simplify_tolerance')
88
self.for_each = args.get('for_each')
89
self.emulate_longitude0 = args.get('emulate_longitude0')
90
if args.get('emulate_longitude0') is None and (self.projection == 'merc' or self.projection =='mill') and self.longitude0 != 0:
91
self.emulate_longitude0 = True
93
if args.get('viewport'):
94
self.viewport = map(lambda s: float(s), args.get('viewport').split(' '))
98
# spatial reference to convert to
99
self.spatialRef = osr.SpatialReference()
100
projString = '+proj='+str(self.projection)+' +a=6381372 +b=6381372 +lat_0=0'
101
if not self.emulate_longitude0:
102
projString += ' +lon_0='+str(self.longitude0)
103
self.spatialRef.ImportFromProj4(projString)
106
if args.get('insets'):
107
self.insets = args.get('insets')
112
def convert(self, data_source, output_file):
113
codes = map(lambda g: g.properties[self.config['code_field']], data_source.geometries)
114
main_codes = copy.copy(codes)
117
for inset in self.insets:
118
insetBbox = self.renderMapInset(data_source, inset['codes'], inset['left'], inset['top'], inset['width'])
119
insetHeight = (insetBbox[3] - insetBbox[1]) * (inset['width'] / (insetBbox[2] - insetBbox[0]))
120
self.map.insets.append({
121
"bbox": [{"x": insetBbox[0], "y": -insetBbox[3]}, {"x": insetBbox[2], "y": -insetBbox[1]}],
122
"left": inset['left'],
124
"width": inset['width'],
125
"height": insetHeight
128
shapely.geometry.box(
129
inset['left'], inset['top'], inset['left'] + inset['width'], inset['top'] + insetHeight
132
for code in inset['codes']:
133
main_codes.remove(code)
135
insetBbox = self.renderMapInset(data_source, main_codes, self.left, self.top, self.width)
136
insetHeight = (insetBbox[3] - insetBbox[1]) * (self.width / (insetBbox[2] - insetBbox[0]))
137
envelope.append( shapely.geometry.box( self.left, self.top, self.left + self.width, self.top + insetHeight ) )
138
mapBbox = shapely.geometry.MultiPolygon( envelope ).bounds
140
self.map.width = mapBbox[2] + mapBbox[0]
141
self.map.height = mapBbox[3] + mapBbox[1]
142
self.map.insets.append({
143
"bbox": [{"x": insetBbox[0], "y": -insetBbox[3]}, {"x": insetBbox[2], "y": -insetBbox[1]}],
147
"height": insetHeight
149
self.map.projection = {"type": self.projection, "centralMeridian": float(self.longitude0)}
151
open(output_file, 'w').write( self.map.getJSCode() )
153
if self.for_each is not None:
155
childConfig = copy.deepcopy(self.for_each)
156
for param in ('input_file', 'output_file', 'where', 'name'):
157
childConfig[param] = childConfig[param].replace('{{code}}', code.lower())
158
converter = Converter(childConfig)
159
converter.convert(childConfig['output_file'])
161
def renderMapInset(self, data_source, codes, left, top, width):
163
geometries = filter(lambda g: g.properties[self.config['code_field']] in codes, data_source.geometries)
164
for geometry in geometries:
165
envelope.append( geometry.geom.envelope )
167
bbox = shapely.geometry.MultiPolygon( envelope ).bounds
169
scale = (bbox[2]-bbox[0]) / width
172
for geometry in geometries:
174
if self.buffer_distance:
175
geom = geom.buffer(self.buffer_distance*scale, 1)
178
if self.simplify_tolerance:
179
geom = geom.simplify(self.simplify_tolerance*scale, preserve_topology=True)
180
if isinstance(geom, shapely.geometry.multipolygon.MultiPolygon):
181
polygons = geom.geoms
185
for polygon in polygons:
187
rings.append(polygon.exterior)
188
rings.extend(polygon.interiors)
190
for pointIndex in range( len(ring.coords) ):
191
point = ring.coords[pointIndex]
193
path += 'M'+str( round( (point[0]-bbox[0]) / scale + left, self.precision) )
194
path += ','+str( round( (bbox[3] - point[1]) / scale + top, self.precision) )
196
path += 'l' + str( round(point[0]/scale - ring.coords[pointIndex-1][0]/scale, self.precision) )
197
path += ',' + str( round(ring.coords[pointIndex-1][1]/scale - point[1]/scale, self.precision) )
199
self.map.addPath(path, geometry.properties[self.config['code_field']], geometry.properties[self.config['name_field']])
204
def __init__(self, geometry, properties):
206
self.properties = properties
209
class GeometryProperty(Variable):
210
operations = set(["equality", "membership"])
212
def __init__(self, name):
215
def equals(self, value, context):
216
return context[self.name] == value
218
def belongs_to(self, value, context):
219
return value in context[self.name]
221
def is_subset(self, value, context):
222
return set(value).issubset(set(context[self.name]))
224
def to_python(self, value):
225
return unicode(value[self.name])
229
def __init__(self, config):
231
"projection": "merc",
234
default_config.update(config)
235
self.config = default_config
237
self.spatialRef = osr.SpatialReference()
238
projString = '+proj='+str(self.config['projection'])+' +a=6381372 +b=6381372 +lat_0=0'
239
#if 'emulate_longitude0' in self.config and not self.config['emulate_longitude0']:
240
projString += ' +lon_0='+str(self.config['longitude0'])
241
self.spatialRef.ImportFromProj4(projString)
244
self.source = ogr.Open( self.config['file_name'], update = 0 )
245
self.layer = self.source.GetLayer(0)
246
if 'filter' in self.config and self.config['filter'] is not None:
247
self.layer.SetAttributeFilter( self.config['filter'].encode('ascii') )
248
self.layer_dfn = self.layer.GetLayerDefn()
251
field_count = self.layer_dfn.GetFieldCount()
252
for field_index in range(field_count):
253
field = self.layer_dfn.GetFieldDefn( field_index )
255
'name': field.GetName(),
256
'type': field.GetType(),
257
'width': field.GetWidth(),
258
'precision': field.GetPrecision()
263
for feature in self.layer:
264
geometry = feature.GetGeometryRef()
265
geometry.TransformTo( self.spatialRef )
266
geometry = shapely.wkb.loads( geometry.ExportToWkb() )
267
if not geometry.is_valid:
268
geometry = geometry.buffer(0)
270
for field in self.fields:
271
properties[field['name']] = feature.GetFieldAsString(field['name']).decode('utf-8')
272
self.geometries.append( Geometry(geometry, properties) )
274
self.layer.ResetReading()
276
self.create_grammar()
278
def create_grammar(self):
279
root_table = SymbolTable("root",
280
map( lambda f: Bind(f['name'], GeometryProperty(f['name'])), self.fields )
288
'is_subset': 'are included in',
292
grammar = Grammar(**tokens)
293
self.parse_manager = EvaluableParseManager(root_table, grammar)
295
def output(self, output):
296
if output.get('format') == 'jvectormap':
297
self.output_jvm(output)
299
self.output_ogr(output)
301
def output_ogr(self, output):
302
driver = ogr.GetDriverByName( 'ESRI Shapefile' )
303
if os.path.exists( output['file_name'] ):
304
driver.DeleteDataSource( output['file_name'] )
305
source = driver.CreateDataSource( output['file_name'] )
306
layer = source.CreateLayer( self.layer_dfn.GetName(),
307
geom_type = self.layer_dfn.GetGeomType(),
308
srs = self.layer.GetSpatialRef() )
310
for field in self.fields:
311
fd = ogr.FieldDefn( str(field['name']), field['type'] )
312
fd.SetWidth( field['width'] )
313
if 'precision' in field:
314
fd.SetPrecision( field['precision'] )
315
layer.CreateField( fd )
317
for geometry in self.geometries:
318
if geometry.geom is not None:
319
feature = ogr.Feature( feature_def = layer.GetLayerDefn() )
320
for index, field in enumerate(self.fields):
321
if field['name'] in geometry.properties:
322
feature.SetField( index, geometry.properties[field['name']].encode('utf-8') )
324
feature.SetField( index, '' )
325
feature.SetGeometryDirectly(
326
ogr.CreateGeometryFromWkb(
332
layer.CreateFeature( feature )
337
def output_jvm(self, output):
338
params = copy.deepcopy(output['params'])
340
"projection": self.config["projection"],
341
"longitude0": self.config["longitude0"]
343
converter = Converter(params)
344
converter.convert(self, output['file_name'])
346
class PolygonSimplifier:
347
def __init__(self, geometries):
348
self.format = '%.8f %.8f'
349
self.tolerance = 0.05
350
self.geometries = geometries
354
for geom in geometries:
358
if isinstance(geom, shapely.geometry.Polygon):
359
polygons.append(geom)
362
polygons.append(polygon)
364
for polygon in polygons:
367
lines.append(polygon.exterior)
368
for line in polygon.interiors:
372
for i in range(len(line.coords)-1):
375
pointFrom = self.format % line.coords[indexFrom]
376
pointTo = self.format % line.coords[indexTo]
377
if pointFrom == pointTo:
379
if not (pointFrom in connections):
380
connections[pointFrom] = {}
381
connections[pointFrom][pointTo] = 1
382
if not (pointTo in connections):
383
connections[pointTo] = {}
384
connections[pointTo][pointFrom] = 1
385
self.connections = connections
386
self.simplifiedLines = {}
387
self.pivotPoints = {}
389
def simplifyRing(self, ring):
390
coords = list(ring.coords)[0:-1]
395
while not isPivot and pointIndex < len(coords):
396
pointStr = self.format % coords[pointIndex]
398
isPivot = ((len(self.connections[pointStr]) > 2) or (pointStr in self.pivotPoints))
399
pointIndex = pointIndex - 1
402
simpleRing = shapely.geometry.LineString(coords).simplify(self.tolerance)
403
if len(simpleRing.coords) <= 2:
406
self.pivotPoints[self.format % coords[0]] = True
407
self.pivotPoints[self.format % coords[-1]] = True
408
simpleLineKey = self.format % coords[0]+':'+self.format % coords[1]+':'+self.format % coords[-1]
409
self.simplifiedLines[simpleLineKey] = simpleRing.coords
412
points = coords[pointIndex:len(coords)]
413
points.extend(coords[0:pointIndex+1])
415
for i in range(1, len(points)):
416
pointStr = self.format % points[i]
417
if ((len(self.connections[pointStr]) > 2) or (pointStr in self.pivotPoints)):
418
line = points[iFrom:i+1]
419
lineKey = self.format % line[-1]+':'+self.format % line[-2]+':'+self.format % line[0]
420
if lineKey in self.simplifiedLines:
421
simpleLine = self.simplifiedLines[lineKey]
422
simpleLine = list(reversed(simpleLine))
424
simpleLine = shapely.geometry.LineString(line).simplify(self.tolerance).coords
425
lineKey = self.format % line[0]+':'+self.format % line[1]+':'+self.format % line[-1]
426
self.simplifiedLines[lineKey] = simpleLine
427
simpleCoords.extend( simpleLine[0:-1] )
429
if len(simpleCoords) <= 2:
432
return shapely.geometry.LineString(simpleCoords)
434
def simplifyPolygon(self, polygon):
435
simpleExtRing = self.simplifyRing(polygon.exterior)
436
if simpleExtRing is None:
439
for ring in polygon.interiors:
440
simpleIntRing = self.simplifyRing(ring)
441
if simpleIntRing is not None:
442
simpleIntRings.append(simpleIntRing)
443
return shapely.geometry.Polygon(simpleExtRing, simpleIntRings)
447
for geom in self.geometries:
451
if isinstance(geom, shapely.geometry.Polygon):
452
polygons.append(geom)
455
polygons.append(polygon)
457
for polygon in polygons:
458
simplePolygon = self.simplifyPolygon(polygon)
459
if not (simplePolygon is None or simplePolygon._geom is None):
460
simplePolygons.append(simplePolygon)
462
if len(simplePolygons) > 0:
463
results.append(shapely.geometry.MultiPolygon(simplePolygons))
470
def __init__(self, config):
474
self.data_sources = {}
475
for action in self.config:
476
getattr(self, action['name'])( action, self.data_sources.get(".") )
478
def read_data(self, config, data_source):
479
self.data_sources["."] = DataSource( config )
480
self.data_sources["."].load_data()
482
def write_data(self, config, data_source):
483
data_source.output( config )
485
def union(self, config, data_source):
488
for geometry in data_source.geometries:
489
if geometry.properties[config['by']] in groups:
490
groups[geometry.properties[config['by']]]['geoms'].append(geometry.geom)
492
groups[geometry.properties[config['by']]] = {
493
'geoms': [geometry.geom],
494
'properties': geometry.properties
497
geometries.append( Geometry(shapely.ops.cascaded_union( groups[key]['geoms'] ), groups[key]['properties']) )
498
data_source.geometries = geometries
500
def merge(self, config, data_source):
502
for rule in config['rules']:
503
expression = data_source.parse_manager.parse( rule['where'] )
504
geometries = filter(lambda g: expression(g.properties), data_source.geometries)
505
geometries = map(lambda g: g.geom, geometries)
506
new_geometries.append( Geometry(shapely.ops.cascaded_union( geometries ), rule['fields']) )
507
data_source.fields = config['fields']
508
data_source.geometries = new_geometries
510
def join_data(self, config, data_source):
511
field_names = [f['name'] for f in config['fields']]
513
data_col = config['data']
515
data_file = open(config['file_name'], 'rb')
516
data_col = csv.reader(data_file, delimiter='\t', quotechar='"')
519
row_dict = dict(zip(field_names, row))
520
data[row_dict.pop(config['on'])] = row_dict
521
for geometry in data_source.geometries:
522
if geometry.properties[config['on']] in data:
523
geometry.properties.update( data[geometry.properties[config['on']]] )
524
field_names = map(lambda f: f['name'], data_source.fields)
525
data_source.fields = data_source.fields + filter(lambda f: f['name'] not in field_names, config['fields'])
527
def remove(self, config, data_source):
528
expression = data_source.parse_manager.parse( config['where'] )
529
data_source.geometries = filter(lambda g: not expression(g.properties), data_source.geometries)
531
def remove_fields(self, config, data_source):
532
data_source.fields = filter(lambda f: f.name not in config['fields'], data_source.fields)
534
def remove_other_fields(self, config, data_source):
535
data_source.fields = filter(lambda f: f['name'] in config['fields'], data_source.fields)
537
def buffer(self, config, data_source):
538
for geometry in data_source.geometries:
539
geometry.geom = geometry.geom.buffer(config['distance'], config['resolution'])
541
def simplify_adjancent_polygons(self, config, data_source):
542
simple_geometries = PolygonSimplifier( map( lambda g: g.geom, data_source.geometries ) ).simplify()
543
for i in range(len(data_source.geometries)):
544
data_source.geometries[i].geom = simple_geometries[i]
546
def intersect_rect(self, config, data_source):
547
transform = osr.CoordinateTransformation( data_source.layer.GetSpatialRef(), data_source.spatialRef )
548
point1 = transform.TransformPoint(config['rect'][0], config['rect'][1])
549
point2 = transform.TransformPoint(config['rect'][2], config['rect'][3])
550
rect = shapely.geometry.box(point1[0], point1[1], point2[0], point2[1])
551
for geometry in data_source.geometries:
552
geometry.geom = geometry.geom.intersection(rect)
554
def remove_small_polygons(self, config, data_source):
555
for geometry in data_source.geometries:
556
if isinstance(geometry.geom, shapely.geometry.multipolygon.MultiPolygon):
557
polygons = geometry.geom.geoms
559
polygons = [geometry.geom]
560
polygons = filter(lambda p: p.area > config['minimal_area'], polygons)
561
if len(polygons) > 0:
562
geometry.geom = shapely.geometry.multipolygon.MultiPolygon(polygons)
567
paramsJson = open(sys.argv[1], 'r').read()
569
paramsJson = sys.stdin.read()
570
paramsJson = json.loads(paramsJson)
572
processor = Processor(paramsJson)