converter.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. #
  2. # jVectorMap version 2.0.4
  3. #
  4. # Copyright 2011-2013, Kirill Lebedev
  5. #
  6. import sys
  7. import shapely.geometry
  8. import shapely.wkb
  9. import shapely.affinity
  10. from osgeo import ogr
  11. from osgeo import osr
  12. import json
  13. import codecs
  14. import copy
  15. class Map:
  16. def __init__(self, name, language):
  17. self.paths = {}
  18. self.name = name
  19. self.language = language
  20. self.width = 0
  21. self.height = 0
  22. self.bbox = []
  23. def addPath(self, path, code, name):
  24. self.paths[code] = {"path": path, "name": name}
  25. def getJSCode(self):
  26. map = {"paths": self.paths, "width": self.width, "height": self.height, "insets": self.insets, "projection": self.projection}
  27. return "jQuery.fn.vectorMap('addMap', '"+self.name+"_"+self.projection['type']+"_"+self.language+"',"+json.dumps(map)+');'
  28. class Converter:
  29. def __init__(self, config):
  30. args = {
  31. 'buffer_distance': -0.4,
  32. 'simplify_tolerance': 0.2,
  33. 'longitude0': 0,
  34. 'projection': 'mill',
  35. 'name': 'world',
  36. 'width': 900,
  37. 'language': 'en',
  38. 'precision': 2,
  39. 'insets': []
  40. }
  41. args.update(config)
  42. self.map = Map(args['name'], args.get('language'))
  43. if args.get('sources'):
  44. self.sources = args['sources']
  45. else:
  46. self.sources = [{
  47. 'input_file': args.get('input_file'),
  48. 'where': args.get('where'),
  49. 'name_field': args.get('name_field'),
  50. 'code_field': args.get('code_field'),
  51. 'input_file_encoding': args.get('input_file_encoding')
  52. }]
  53. default_source = {
  54. 'where': '',
  55. 'name_field': 0,
  56. 'code_field': 1,
  57. 'input_file_encoding': 'iso-8859-1'
  58. }
  59. for index in range(len(self.sources)):
  60. for key in default_source:
  61. if self.sources[index].get(key) is None:
  62. self.sources[index][key] = default_source[key]
  63. self.features = {}
  64. self.width = args.get('width')
  65. self.minimal_area = args.get('minimal_area')
  66. self.longitude0 = float(args.get('longitude0'))
  67. self.projection = args.get('projection')
  68. self.precision = args.get('precision')
  69. self.buffer_distance = args.get('buffer_distance')
  70. self.simplify_tolerance = args.get('simplify_tolerance')
  71. self.for_each = args.get('for_each')
  72. self.emulate_longitude0 = args.get('emulate_longitude0')
  73. if args.get('emulate_longitude0') is None and (self.projection == 'merc' or self.projection =='mill') and self.longitude0 != 0:
  74. self.emulate_longitude0 = True
  75. if args.get('viewport'):
  76. self.viewport = map(lambda s: float(s), args.get('viewport').split(' '))
  77. else:
  78. self.viewport = False
  79. # spatial reference to convert to
  80. self.spatialRef = osr.SpatialReference()
  81. projString = '+proj='+str(self.projection)+' +a=6381372 +b=6381372 +lat_0=0'
  82. if not self.emulate_longitude0:
  83. projString += ' +lon_0='+str(self.longitude0)
  84. self.spatialRef.ImportFromProj4(projString)
  85. # handle map insets
  86. if args.get('insets'):
  87. self.insets = args.get('insets')
  88. else:
  89. self.insets = []
  90. def loadData(self):
  91. for sourceConfig in self.sources:
  92. self.loadDataSource( sourceConfig )
  93. def loadDataSource(self, sourceConfig):
  94. source = ogr.Open( sourceConfig['input_file'] )
  95. layer = source.GetLayer(0)
  96. layer.SetAttributeFilter( sourceConfig['where'].encode('ascii') )
  97. self.viewportRect = False
  98. transformation = osr.CoordinateTransformation( layer.GetSpatialRef(), self.spatialRef )
  99. if self.viewport:
  100. layer.SetSpatialFilterRect( *self.viewport )
  101. point1 = transformation.TransformPoint(self.viewport[0], self.viewport[1])
  102. point2 = transformation.TransformPoint(self.viewport[2], self.viewport[3])
  103. self.viewportRect = shapely.geometry.box(point1[0], point1[1], point2[0], point2[1])
  104. layer.ResetReading()
  105. codes = {}
  106. if self.emulate_longitude0:
  107. meridian = -180 + self.longitude0
  108. p1 = transformation.TransformPoint(-180, 89)
  109. p2 = transformation.TransformPoint(meridian, -89)
  110. left = shapely.geometry.box(p1[0], p1[1], p2[0], p2[1])
  111. p3 = transformation.TransformPoint(meridian, 89)
  112. p4 = transformation.TransformPoint(180, -89)
  113. right = shapely.geometry.box(p3[0], p3[1], p4[0], p4[1])
  114. # load features
  115. nextCode = 0
  116. for feature in layer:
  117. geometry = feature.GetGeometryRef()
  118. geometryType = geometry.GetGeometryType()
  119. if geometryType == ogr.wkbPolygon or geometryType == ogr.wkbMultiPolygon:
  120. geometry.TransformTo( self.spatialRef )
  121. shapelyGeometry = shapely.wkb.loads( geometry.ExportToWkb() )
  122. if not shapelyGeometry.is_valid:
  123. shapelyGeometry = shapelyGeometry.buffer(0, 1)
  124. if self.emulate_longitude0:
  125. leftPart = shapely.affinity.translate(shapelyGeometry.intersection(left), p4[0] - p3[0])
  126. rightPart = shapely.affinity.translate(shapelyGeometry.intersection(right), p1[0] - p2[0])
  127. shapelyGeometry = leftPart.buffer(0.1, 1).union(rightPart.buffer(0.1, 1)).buffer(-0.1, 1)
  128. if not shapelyGeometry.is_valid:
  129. shapelyGeometry = shapelyGeometry.buffer(0, 1)
  130. shapelyGeometry = self.applyFilters(shapelyGeometry)
  131. if shapelyGeometry:
  132. name = feature.GetFieldAsString(str(sourceConfig.get('name_field'))).decode(sourceConfig.get('input_file_encoding'))
  133. code = feature.GetFieldAsString(str(sourceConfig.get('code_field'))).decode(sourceConfig.get('input_file_encoding'))
  134. if code in codes:
  135. code = '_' + str(nextCode)
  136. nextCode += 1
  137. codes[code] = name
  138. self.features[code] = {"geometry": shapelyGeometry, "name": name, "code": code}
  139. else:
  140. raise Exception, "Wrong geometry type: "+geometryType
  141. def convert(self, outputFile):
  142. print 'Generating '+outputFile
  143. self.loadData()
  144. codes = self.features.keys()
  145. main_codes = copy.copy(codes)
  146. self.map.insets = []
  147. envelope = []
  148. for inset in self.insets:
  149. insetBbox = self.renderMapInset(inset['codes'], inset['left'], inset['top'], inset['width'])
  150. insetHeight = (insetBbox[3] - insetBbox[1]) * (inset['width'] / (insetBbox[2] - insetBbox[0]))
  151. self.map.insets.append({
  152. "bbox": [{"x": insetBbox[0], "y": -insetBbox[3]}, {"x": insetBbox[2], "y": -insetBbox[1]}],
  153. "left": inset['left'],
  154. "top": inset['top'],
  155. "width": inset['width'],
  156. "height": insetHeight
  157. })
  158. envelope.append(
  159. shapely.geometry.box(
  160. inset['left'], inset['top'], inset['left'] + inset['width'], inset['top'] + insetHeight
  161. )
  162. )
  163. for code in inset['codes']:
  164. main_codes.remove(code)
  165. insetBbox = self.renderMapInset(main_codes, 0, 0, self.width)
  166. insetHeight = (insetBbox[3] - insetBbox[1]) * (self.width / (insetBbox[2] - insetBbox[0]))
  167. envelope.append( shapely.geometry.box( 0, 0, self.width, insetHeight ) )
  168. mapBbox = shapely.geometry.MultiPolygon( envelope ).bounds
  169. self.map.width = mapBbox[2] - mapBbox[0]
  170. self.map.height = mapBbox[3] - mapBbox[1]
  171. self.map.insets.append({
  172. "bbox": [{"x": insetBbox[0], "y": -insetBbox[3]}, {"x": insetBbox[2], "y": -insetBbox[1]}],
  173. "left": 0,
  174. "top": 0,
  175. "width": self.width,
  176. "height": insetHeight
  177. })
  178. self.map.projection = {"type": self.projection, "centralMeridian": float(self.longitude0)}
  179. open(outputFile, 'w').write( self.map.getJSCode() )
  180. if self.for_each is not None:
  181. for code in codes:
  182. childConfig = copy.deepcopy(self.for_each)
  183. for param in ('input_file', 'output_file', 'where', 'name'):
  184. childConfig[param] = childConfig[param].replace('{{code}}', code.lower())
  185. converter = Converter(childConfig)
  186. converter.convert(childConfig['output_file'])
  187. def renderMapInset(self, codes, left, top, width):
  188. envelope = []
  189. for code in codes:
  190. envelope.append( self.features[code]['geometry'].envelope )
  191. bbox = shapely.geometry.MultiPolygon( envelope ).bounds
  192. scale = (bbox[2]-bbox[0]) / width
  193. # generate SVG paths
  194. for code in codes:
  195. feature = self.features[code]
  196. geometry = feature['geometry']
  197. if self.buffer_distance:
  198. geometry = geometry.buffer(self.buffer_distance*scale, 1)
  199. if geometry.is_empty:
  200. continue
  201. if self.simplify_tolerance:
  202. geometry = geometry.simplify(self.simplify_tolerance*scale, preserve_topology=True)
  203. if isinstance(geometry, shapely.geometry.multipolygon.MultiPolygon):
  204. polygons = geometry.geoms
  205. else:
  206. polygons = [geometry]
  207. path = ''
  208. for polygon in polygons:
  209. rings = []
  210. rings.append(polygon.exterior)
  211. rings.extend(polygon.interiors)
  212. for ring in rings:
  213. for pointIndex in range( len(ring.coords) ):
  214. point = ring.coords[pointIndex]
  215. if pointIndex == 0:
  216. path += 'M'+str( round( (point[0]-bbox[0]) / scale + left, self.precision) )
  217. path += ','+str( round( (bbox[3] - point[1]) / scale + top, self.precision) )
  218. else:
  219. path += 'l' + str( round(point[0]/scale - ring.coords[pointIndex-1][0]/scale, self.precision) )
  220. path += ',' + str( round(ring.coords[pointIndex-1][1]/scale - point[1]/scale, self.precision) )
  221. path += 'Z'
  222. self.map.addPath(path, feature['code'], feature['name'])
  223. return bbox
  224. def applyFilters(self, geometry):
  225. if self.viewportRect:
  226. geometry = self.filterByViewport(geometry)
  227. if not geometry:
  228. return False
  229. if self.minimal_area:
  230. geometry = self.filterByMinimalArea(geometry)
  231. if not geometry:
  232. return False
  233. return geometry
  234. def filterByViewport(self, geometry):
  235. try:
  236. return geometry.intersection(self.viewportRect)
  237. except shapely.geos.TopologicalError:
  238. return False
  239. def filterByMinimalArea(self, geometry):
  240. if isinstance(geometry, shapely.geometry.multipolygon.MultiPolygon):
  241. polygons = geometry.geoms
  242. else:
  243. polygons = [geometry]
  244. polygons = filter(lambda p: p.area > self.minimal_area, polygons)
  245. return shapely.geometry.multipolygon.MultiPolygon(polygons)
  246. args = {}
  247. if len(sys.argv) > 1:
  248. paramsJson = open(sys.argv[1], 'r').read()
  249. else:
  250. paramsJson = sys.stdin.read()
  251. paramsJson = json.loads(paramsJson)
  252. converter = Converter(paramsJson)
  253. converter.convert(paramsJson['output_file'])