From 6aa10716a544eb761a1d3b6b6c336f8081f95ffb Mon Sep 17 00:00:00 2001 From: Jeffrey Warren Date: Mon, 7 Jun 2010 18:14:26 -0400 Subject: [PATCH] lots of changes, plus warps (finished warpables) but not yet functioning fully --- app/models/warp.rb | 86 ++ .../20100505153420_add_warpable_deleted.rb | 9 + db/migrate/20100531175531_create_warpeds.rb | 20 + db/migrate/20100531175725_create_warps.rb | 19 + lib/cartagen.rb | 18 +- lib/ogr2osm/SimpleXMLWriter.py | 279 +++++++ lib/ogr2osm/SimpleXMLWriter.pyc | Bin 0 -> 5858 bytes lib/ogr2osm/ogr2osm.py | 769 ++++++++++++++++++ lib/ogr2osm/translations/carreteras_gv.py | 76 ++ lib/ogr2osm/translations/ithaca_haiti.py | 55 ++ test/fixtures/warps.yml | 19 + test/unit/warp_test.rb | 8 + 12 files changed, 1357 insertions(+), 1 deletion(-) create mode 100644 app/models/warp.rb create mode 100644 db/migrate/20100505153420_add_warpable_deleted.rb create mode 100644 db/migrate/20100531175531_create_warpeds.rb create mode 100644 db/migrate/20100531175725_create_warps.rb create mode 100644 lib/ogr2osm/SimpleXMLWriter.py create mode 100644 lib/ogr2osm/SimpleXMLWriter.pyc create mode 100755 lib/ogr2osm/ogr2osm.py create mode 100644 lib/ogr2osm/translations/carreteras_gv.py create mode 100644 lib/ogr2osm/translations/ithaca_haiti.py create mode 100644 test/fixtures/warps.yml create mode 100644 test/unit/warp_test.rb diff --git a/app/models/warp.rb b/app/models/warp.rb new file mode 100644 index 00000000..5b3fda77 --- /dev/null +++ b/app/models/warp.rb @@ -0,0 +1,86 @@ +class Warp < ActiveRecord::Base + + has_attachment :content_type => :image, + :storage => :file_system,:path_prefix => 'public/warpables', + #:storage => :s3, + :max_size => 5.megabytes, + # :resize_to => '320x200>', + :processor => :image_science, + :thumbnails => { :medium => '500x375', :small => '240x180', :thumb => '100x100>' } + + # validates_as_attachment + + def validate + errors.add_to_base("You must choose a file to upload") unless self.filename + + unless self.filename == nil + + # Images should only be GIF, JPEG, or PNG + [:content_type].each do |attr_name| + enum = attachment_options[attr_name] + unless enum.nil? || enum.include?(send(attr_name)) + errors.add_to_base("You can only upload images (GIF, JPEG, or PNG)") + end + end + + # Images should be less than 5 MB + [:size].each do |attr_name| + enum = attachment_options[attr_name] + unless enum.nil? || enum.include?(send(attr_name)) + errors.add_to_base("Images should be smaller than 5 MB in size") + end + end + + end + end + + def url=(value) + self.uploaded_data = UrlUpload.new(value) + end + +end + + +require 'open-uri' + +# Make it always write to tempfiles, never StringIO +OpenURI::Buffer.module_eval { + remove_const :StringMax + const_set :StringMax, 0 +} + +class UrlUpload + EXTENSIONS = { + "image/jpeg" => ["jpg", "jpeg", "jpe"], + "image/gif" => ["gif"], + "image/png" => ["png"] + } + attr_reader :original_filename, :attachment_data + def initialize(url) + @attachment_data = open(url) + @original_filename = determine_filename + end + + # Pass things like size, content_type, path on to the downloaded file + def method_missing(symbol, *args) + if self.attachment_data.respond_to? symbol + self.attachment_data.send symbol, *args + else + super + end + end + + private + def determine_filename + # Grab the path - even though it could be a script and not an actual file + path = self.attachment_data.base_uri.path + # Get the filename from the path, make it lowercase to handle those + # crazy Win32 servers with all-caps extensions + filename = File.basename(path).downcase + # If the file extension doesn't match the content type, add it to the end, changing any existing .'s to _ + filename = [filename.gsub(/\./, "_"), EXTENSIONS[self.content_type].first].join(".") unless EXTENSIONS[self.content_type].any? {|ext| filename.ends_with?("." + ext) } + # Return the result + filename + end + end + diff --git a/db/migrate/20100505153420_add_warpable_deleted.rb b/db/migrate/20100505153420_add_warpable_deleted.rb new file mode 100644 index 00000000..47d5de9e --- /dev/null +++ b/db/migrate/20100505153420_add_warpable_deleted.rb @@ -0,0 +1,9 @@ +class AddWarpableDeleted < ActiveRecord::Migration + def self.up + add_column :warpables, :deleted, :boolean, :default => false, :null => false + end + + def self.down + remove_column :warpables, :deleted + end +end diff --git a/db/migrate/20100531175531_create_warpeds.rb b/db/migrate/20100531175531_create_warpeds.rb new file mode 100644 index 00000000..248e334f --- /dev/null +++ b/db/migrate/20100531175531_create_warpeds.rb @@ -0,0 +1,20 @@ +class CreateWarpeds < ActiveRecord::Migration + def self.up + create_table :warpeds do |t| + t.integer :parent_id + t.string :content_type + t.string :filename + t.string :thumbnail + t.integer :size + t.integer :width + t.integer :height + t.string :transform_type + + t.timestamps + end + end + + def self.down + drop_table :warpeds + end +end diff --git a/db/migrate/20100531175725_create_warps.rb b/db/migrate/20100531175725_create_warps.rb new file mode 100644 index 00000000..faada095 --- /dev/null +++ b/db/migrate/20100531175725_create_warps.rb @@ -0,0 +1,19 @@ +class CreateWarps < ActiveRecord::Migration + def self.up + create_table :warps do |t| + t.integer :parent_id + t.string :content_type + t.string :filename + t.string :thumbnail + t.integer :size + t.integer :width + t.integer :height + + t.timestamps + end + end + + def self.down + drop_table :warps + end +end diff --git a/lib/cartagen.rb b/lib/cartagen.rb index 1ea9b69f..a1d9ee3b 100644 --- a/lib/cartagen.rb +++ b/lib/cartagen.rb @@ -5,6 +5,22 @@ class Cartagen string.slice!(word+' ') word end + + def self.spherical_mercator_lon_to_x(lon,center_lon,scale_factor=10000) + (lon - center_lon) * -1 * scale_factor + end + + def self.spherical_mercator_x_to_lon(x,center_lon,scale_factor=10000) + (x/(-1*scale_factor)) + center_lon + end + + def self.spherical_mercator_lat_to_y(lat,scale_factor=10000) + 180/Math::PI * Math.log(Math.tan(Math::PI/4+lat*(Math::PI/180)/2)) * scale_factor + end + + def self.spherical_mercator_y_to_lat(y,scale_factor=10000) + 180/Math::PI * (2 * Math.atan(Math.exp(y/scale_factor*Math::PI/180)) - Math::PI/2) + end # collects coastline ways into collected_way relations; # see http://wiki.openstreetmap.org/wiki/Relations/Proposed/Collected_Ways @@ -64,4 +80,4 @@ class Cartagen # end # end -end \ No newline at end of file +end diff --git a/lib/ogr2osm/SimpleXMLWriter.py b/lib/ogr2osm/SimpleXMLWriter.py new file mode 100644 index 00000000..af3023f4 --- /dev/null +++ b/lib/ogr2osm/SimpleXMLWriter.py @@ -0,0 +1,279 @@ +# +# SimpleXMLWriter +# $Id: SimpleXMLWriter.py 2312 2005-03-02 18:13:39Z fredrik $ +# +# a simple XML writer +# +# history: +# 2001-12-28 fl created +# 2002-11-25 fl fixed attribute encoding +# 2002-12-02 fl minor fixes for 1.5.2 +# 2004-06-17 fl added pythondoc markup +# 2004-07-23 fl added flush method (from Jay Graves) +# 2004-10-03 fl added declaration method +# +# Copyright (c) 2001-2004 by Fredrik Lundh +# +# fredrik@pythonware.com +# http://www.pythonware.com +# +# -------------------------------------------------------------------- +# The SimpleXMLWriter module is +# +# Copyright (c) 2001-2004 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# -------------------------------------------------------------------- + +## +# Tools to write XML files, without having to deal with encoding +# issues, well-formedness, etc. +#

+# The current version does not provide built-in support for +# namespaces. To create files using namespaces, you have to provide +# "xmlns" attributes and explicitly add prefixes to tags and +# attributes. +# +#

Patterns

+# +# The following example generates a small XHTML document. +#
+#
+# from elementtree.SimpleXMLWriter import XMLWriter
+# import sys
+#
+# w = XMLWriter(sys.stdout)
+#
+# html = w.start("html")
+#
+# w.start("head")
+# w.element("title", "my document")
+# w.element("meta", name="generator", value="my application 1.0")
+# w.end()
+#
+# w.start("body")
+# w.element("h1", "this is a heading")
+# w.element("p", "this is a paragraph")
+#
+# w.start("p")
+# w.data("this is ")
+# w.element("b", "bold")
+# w.data(" and ")
+# w.element("i", "italic")
+# w.data(".")
+# w.end("p")
+#
+# w.close(html)
+# 
+## + +import re, sys, string + +try: + unicode("") +except NameError: + def encode(s, encoding): + # 1.5.2: application must use the right encoding + return s + _escape = re.compile(r"[&<>\"\x80-\xff]+") # 1.5.2 +else: + def encode(s, encoding): + return s.encode(encoding) + _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]+"')) + +def encode_entity(text, pattern=_escape): + # map reserved and non-ascii characters to numerical entities + def escape_entities(m): + out = [] + for char in m.group(): + out.append("&#%d;" % ord(char)) + return string.join(out, "") + return encode(pattern.sub(escape_entities, text), "ascii") + +del _escape + +# +# the following functions assume an ascii-compatible encoding +# (or "utf-16") + +def escape_cdata(s, encoding=None, replace=string.replace): + s = replace(s, "&", "&") + s = replace(s, "<", "<") + s = replace(s, ">", ">") + if encoding: + try: + return encode(s, encoding) + except UnicodeError: + return encode_entity(s) + return s + +def escape_attrib(s, encoding=None, replace=string.replace): + s = replace(s, "&", "&") + s = replace(s, "'", "'") + s = replace(s, "\"", """) + s = replace(s, "<", "<") + s = replace(s, ">", ">") + if encoding: + try: + return encode(s, encoding) + except UnicodeError: + return encode_entity(s) + return s + +## +# XML writer class. +# +# @param file A file or file-like object. This object must implement +# a write method that takes an 8-bit string. +# @param encoding Optional encoding. + +class XMLWriter: + + def __init__(self, file, encoding="us-ascii"): + if not hasattr(file, "write"): + file = open(file, "w") + self.__write = file.write + if hasattr(file, "flush"): + self.flush = file.flush + self.__open = 0 # true if start tag is open + self.__tags = [] + self.__data = [] + self.__encoding = encoding + + def __flush(self): + # flush internal buffers + if self.__open: + self.__write(">") + self.__open = 0 + if self.__data: + data = string.join(self.__data, "") + self.__write(escape_cdata(data, self.__encoding)) + self.__data = [] + + ## + # Writes an XML declaration. + + def declaration(self): + encoding = self.__encoding + if encoding == "us-ascii" or encoding == "utf-8": + self.__write("\n") + else: + self.__write("\n" % encoding) + + ## + # Opens a new element. Attributes can be given as keyword + # arguments, or as a string/string dictionary. You can pass in + # 8-bit strings or Unicode strings; the former are assumed to use + # the encoding passed to the constructor. The method returns an + # opaque identifier that can be passed to the close method, + # to close all open elements up to and including this one. + # + # @param tag Element tag. + # @param attrib Attribute dictionary. Alternatively, attributes + # can be given as keyword arguments. + # @return An element identifier. + + def start(self, tag, attrib={}, **extra): + self.__flush() + tag = escape_cdata(tag, self.__encoding) + self.__data = [] + self.__tags.append(tag) + self.__write("<%s" % tag) + if attrib or extra: + attrib = attrib.copy() + attrib.update(extra) + attrib = attrib.items() + attrib.sort() + for k, v in attrib: + k = escape_cdata(k, self.__encoding) + v = escape_attrib(v, self.__encoding) + self.__write(" %s=\"%s\"" % (k, v)) + self.__open = 1 + return len(self.__tags)-1 + + ## + # Adds a comment to the output stream. + # + # @param comment Comment text, as an 8-bit string or Unicode string. + + def comment(self, comment): + self.__flush() + self.__write("\n" % escape_cdata(comment, self.__encoding)) + + ## + # Adds character data to the output stream. + # + # @param text Character data, as an 8-bit string or Unicode string. + + def data(self, text): + self.__data.append(text) + + ## + # Closes the current element (opened by the most recent call to + # start). + # + # @param tag Element tag. If given, the tag must match the start + # tag. If omitted, the current element is closed. + + def end(self, tag=None): + if tag: + assert self.__tags, "unbalanced end(%s)" % tag + assert escape_cdata(tag, self.__encoding) == self.__tags[-1],\ + "expected end(%s), got %s" % (self.__tags[-1], tag) + else: + assert self.__tags, "unbalanced end()" + tag = self.__tags.pop() + if self.__data: + self.__flush() + elif self.__open: + self.__open = 0 + self.__write(" />") + return + self.__write("" % tag) + + ## + # Closes open elements, up to (and including) the element identified + # by the given identifier. + # + # @param id Element identifier, as returned by the start method. + + def close(self, id): + while len(self.__tags) > id: + self.end() + + ## + # Adds an entire element. This is the same as calling start, + # data, and end in sequence. The text argument + # can be omitted. + + def element(self, tag, text=None, attrib={}, **extra): + apply(self.start, (tag, attrib), extra) + if text: + self.data(text) + self.end() + + ## + # Flushes the output stream. + + def flush(self): + pass # replaced by the constructor diff --git a/lib/ogr2osm/SimpleXMLWriter.pyc b/lib/ogr2osm/SimpleXMLWriter.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fdbf1bcd3d785c0247f762d42b9dbda4ef199427 GIT binary patch literal 5858 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 + + + This piece of crap^H^H^H^Hsoftware is supposed to take just about any vector file + as an input thanks to the magic of the OGR libraries, and then output a pretty OSM XML + file with that data. + + The cool part is that it will detect way segments shared between several ways, so + it will build relations outof thin air. This simplifies the structure of boundaries, for + example. + + It is also able to translate attributes to tags, though there is only one such translation + scheme by now. In order to translate your own datasets, you should have some basic + understanding of python programming. See the files in the translation/ directory. + + An outstanding issue is that elevation in 2.5D features (that can be generated by + reprojecting) is ignored completely. + + Usage: specify a filename to be converted (its extension will be changed to .osm), and the + the projection the source data is in. You can specify the source projection by using either + an EPSG code or a Proj.4 string. + + If the projection is not specified, ogr2osm will try to fetch it from the source data. If + there is no projection information in the source data, this will assume EPSG:4326 (WGS84 + latitude-longitude). + + python ogr2osm.py [options] [filename] + + Options: + -e, --epsg=... EPSG code, forcing the source data projection + -p, --proj4=... PROJ4 string, forcing the source data projection + -v, --verbose Shows some seemingly random characters dancing in the screen + for every feature that's being worked on. + -h, --help Show this message + -d, --debug-tags Outputs the tags for every feature parsed + -a, --attribute-stats Outputs a summary of the different tags / attributes encountered + -t, --translation Select the attribute-tags translation method. + See the translations/ diredtory for valid values. + + (-e and -p are mutually exclusive. If both are specified, only the last one will be + taken into account) + + For example, if the shapefile foobar.shp has projection EPSG:23030, do: + + python ogr2osm.py foobar.shp -e 23030 + + This will do an in-the-fly reprojection from EPSG:23030 to EPSG:4326, and write a file + called "foobar.osm" + + +##################################################################################### +# "THE BEER-WARE LICENSE": # +# wrote this file. As long as you retain this notice you # +# can do whatever you want with this stuff. If we meet some day, and you think # +# this stuff is worth it, you can buy me a beer in return. # +##################################################################################### +""" + + +import sys +import os +import getopt +from SimpleXMLWriter import XMLWriter + +try: + from osgeo import ogr +except: + import ogr + +try: + from osgeo import osr +except: + import osr + +# Some needed constants +from ogr import wkbPoint +from ogr import wkbLineString +from ogr import wkbPolygon +from ogr import wkbMultiPoint +from ogr import wkbMultiLineString +from ogr import wkbMultiPolygon +from ogr import wkbGeometryCollection + +from ogr import wkbUnknown +from ogr import wkbNone + +from ogr import wkbPoint25D +from ogr import wkbLineString25D +from ogr import wkbPolygon25D +from ogr import wkbMultiPoint25D +from ogr import wkbMultiLineString25D +from ogr import wkbMultiPolygon25D +from ogr import wkbGeometryCollection25D + + + +# Default options +sourceEPSG = 4326 +sourceProj4 = None +detectProjection = True +useEPSG = False +useProj4 = False +showProgress = False +debugTags = False +attributeStats = False +translationMethod = None + +# Fetch command line parameters: file and source projection +try: + (opts, args) = getopt.getopt(sys.argv[1:], "e:p:hvdt:a", ["epsg","proj4","help","verbose","debug-tags","attribute-stats","translation"]) +except getopt.GetoptError: + print __doc__ + sys.exit(2) +for opt, arg in opts: + if opt in ("-h", "--help"): + print __doc__ + sys.exit() + elif opt in ("-p", "--proj4"): + sourceProj4 = arg + useProj4 = True + useEPSG = False + detectProjection = False + elif opt in ("-e", "--epsg"): + try: + sourceEPSG = int(arg) + except: + print "Error: EPSG code must be numeric (e.g. '4326' instead of 'epsg:4326')" + sys.exit(1) + detectProjection = False + useEPSG = True + useProj4 = False + elif opt in ("-v", "--verbose"): + showProgress=True + elif opt in ("-d", "--debug-tags"): + debugTags=True + elif opt in ("-a", "--attribute-stats"): + attributeStats=True + attributeStatsTable = {} + elif opt in ("-t", "--translation"): + translationMethod = arg + else: + print "Unknown option " + opt + +print (opts,args) +file = args[0] +fileExtension = file.split('.')[-1] + + +# FIXME: really complete this table +if fileExtension == 'shp': + driver = ogr.GetDriverByName('ESRI Shapefile'); +elif fileExtension == 'gpx': + driver = ogr.GetDriverByName('GPX'); +elif fileExtension == 'dgn': + driver = ogr.GetDriverByName('DGN'); +elif fileExtension == 'gml': + driver = ogr.GetDriverByName('GML'); +elif fileExtension == 'csv': + driver = ogr.GetDriverByName('CSV'); +elif fileExtension == 'sqlite': + driver = ogr.GetDriverByName('SQLite'); +elif fileExtension == 'kml': + driver = ogr.GetDriverByName('KML'); +#elif fileExtension == 'kmz': + #driver = ogr.GetDriverByName('KML'); +else: + print "Error: extension " + fileExtension + " is invalid or not implemented yet." + + +# Strip directories from output file name +slashPosition = file.rfind('/') +if slashPosition != -1: + #print slashPosition + outputFile = file[slashPosition+1:] + #print outputFile + #print len(fileExtension) +else: + outputFile = file + +outputFile = outputFile[: -len(fileExtension) ] + 'osm' + + +# 0 means read-only +dataSource = driver.Open(file,0); + +if dataSource is None: + print 'Could not open ' + file + sys.exit(1) + + +print +print "Preparing to convert file " + file + " (extension is " + fileExtension + ") into " + outputFile + +if detectProjection: + print "Will try to detect projection from source metadata, or fall back to EPSG:4326" +elif useEPSG: + print "Will assume that source data is in EPSG:" + str(sourceEPSG) +elif useProj4: + print "Will assume that source data has the Proj.4 string: " + sourceProj4 + +if showProgress: + print "Verbose mode is on. Get ready to see lots of dots." + +if debugTags: + print "Tag debugging is on. Get ready to see lots of stuff." + + +# Some variables to hold stuff... +nodeIDsByXY = {} +nodeTags = {} +nodeCoords = {} +nodeRefs = {} +segmentNodes = {} +segmentIDByNodes = {} +segmentRefs = {} +areaRings = {} +areaTags = {} +lineSegments = {} +lineTags = {} + +# nodeIDsByXY holds a node ID, given a set of coordinates (useful for looking for duplicated nodes) +# nodeTags holds the tag pairs given a node ID +# nodeCoords holds the coordinates of a given node ID (redundant if nodeIDsByXY is properly iterated through) +# nodeRefs holds up the IDs of any segment referencing (containing) a given node ID, as a dictionary +# segmentNodes holds up the node IDs for a given segment ID +# segmentIDByNodes holds up segment IDs for a given pair of node IDs (useful for looking for duplicated segments) +# segmentRefs holds up the IDs of any ways or areas referencing (containing) a given segment ID, as a dictionary with segment IDs as keys, and a boolean as value (the bool is a flag indicating whether the segment is an existing segment, but reversed - will probably screw things up with oneway=yes stuff) +# areaRings holds up the rings, as a list of segments, for a given area ID +# areaTags holds up the tags for a given area ID +# lineSegments and lineTags work pretty much as areaRings and areaTags (only that lineSegments is a list, and areaRings is a list of lists) + + +# Stuff needed for locating translation methods +if translationMethod: + try: + sys.path.append(os.getcwd() + "/translations") + module = __import__(translationMethod) + translateAttributes = module.translateAttributes + translateAttributes([]) + except: + print "Could not load translation method " + translationMethod + ". Check the translations/ directory for valid values." + sys.exit(-1) + print "Successfully loaded " + translationMethod + " translation method." +else: + # If no function has been defined, perform no translation: just copy everything. + translateAttributes = lambda(attrs): attrs + + + +elementIdCounter = -1 +nodeCount = 0 +segmentCount = 0 +lineCount = 0 +areaCount = 0 +segmentJoinCount = 0 + + +print +print "Parsing features" + + +# Some aux stuff for parsing the features into the data arrays + +def addNode(x,y,tags = {}): + "Given x,y, returns the ID of an existing node there, or creates it and returns the new ID. Node will be updated with the optional tags." + global elementIdCounter, nodeCount, nodeCoords, nodeIDsByXT, nodeTags, nodeCoords + + if (x,y) in nodeIDsByXY: + # Node already exists, merge tags + #print + #print "Warning, node already exists" + nodeID = nodeIDsByXY[(x,y)] + try: + nodeTags[nodeID].update(tags) + except: + nodeTags[nodeID]=tags + return nodeID + else: + # Allocate a new node + nodeID = elementIdCounter + elementIdCounter = elementIdCounter - 1 + + nodeTags[nodeID]=tags + nodeIDsByXY[(x,y)] = nodeID + nodeCoords[nodeID] = (x,y) + nodeCount = nodeCount +1 + return nodeID + + +def lineStringToSegments(geometry,references): + "Given a LineString geometry, will create the appropiate segments. It will add the optional tags and will not check for duplicate segments. Needs a line or area ID for updating the segment references. Returns a list of segment IDs." + global elementIdCounter, segmentCount, segmentNodes, segmentTags, showProgress, nodeRefs, segmentRefs, segmentIDByNodes + + result = [] + + (lastx,lasty,z) = geometry.GetPoint(0) + lastNodeID = addNode(lastx,lasty) + + for k in range(1,geometry.GetPointCount()): + + (newx,newy,z) = geometry.GetPoint(k) + newNodeID = addNode(newx,newy) + + if (lastNodeID, newNodeID) in segmentIDByNodes: + if showProgress: sys.stdout.write(u"-") + segmentID = segmentIDByNodes[(lastNodeID, newNodeID)] + reversed = False + #print + #print "Duplicated segment" + elif (newNodeID, lastNodeID) in segmentIDByNodes: + if showProgress: sys.stdout.write(u"_") + segmentID = segmentIDByNodes[(newNodeID, lastNodeID)] + reversed = True + #print + #print "Duplicated reverse segment" + else: + if showProgress: sys.stdout.write('.') + segmentID = elementIdCounter + + elementIdCounter = elementIdCounter - 1 + segmentCount = segmentCount +1 + segmentNodes[segmentID] = [ lastNodeID, newNodeID ] + segmentIDByNodes[(lastNodeID, newNodeID)] = segmentID + reversed = False + + try: + nodeRefs[lastNodeID].update({segmentID:True}) + except: + nodeRefs[lastNodeID]={segmentID:True} + try: + nodeRefs[newNodeID].update({segmentID:True}) + except: + nodeRefs[newNodeID]={segmentID:True} + + + try: + segmentRefs[segmentID].update({references:reversed}) + except: + segmentRefs[segmentID]={references:reversed} + + result.append(segmentID) + + # FIXME + segmentRefs + + lastNodeID = newNodeID + return result + + + + + + + +# Let's dive into the OGR data source and fetch the features + +for i in range(dataSource.GetLayerCount()): + layer = dataSource.GetLayer(i) + layer.ResetReading() + + spatialRef = None + if detectProjection: + spatialRef = layer.GetSpatialRef() + if spatialRef != None: + print "Detected projection metadata:" + print spatialRef + else: + print "No projection metadata, falling back to EPSG:4326" + elif useEPSG: + spatialRef = osr.SpatialReference() + spatialRef.ImportFromEPSG(sourceEPSG) + elif useProj4: + spatialRef = osr.SpatialReference() + spatialRef.ImportFromProj4(sourceProj4) + + + + if spatialRef == None: # No source proj specified yet? Then default to do no reprojection. + # Some python magic: skip reprojection altogether by using a dummy lamdba funcion. Otherwise, the lambda will be a call to the OGR reprojection stuff. + reproject = lambda(geometry): None + else: + destSpatialRef = osr.SpatialReference() + destSpatialRef.ImportFromEPSG(4326) # Destionation projection will *always* be EPSG:4326, WGS84 lat-lon + coordTrans = osr.CoordinateTransformation(spatialRef,destSpatialRef) + reproject = lambda(geometry): geometry.Transform(coordTrans) + + + featureDefinition = layer.GetLayerDefn() + + fieldNames = [] + fieldCount = featureDefinition.GetFieldCount(); + + for j in range(fieldCount): + #print featureDefinition.GetFieldDefn(j).GetNameRef() + fieldNames.append (featureDefinition.GetFieldDefn(j).GetNameRef()) + if attributeStats: + attributeStatsTable.update({featureDefinition.GetFieldDefn(j).GetNameRef():{} }) + + print + print fieldNames + print "Got layer field definitions" + + #print "Feature definition: " + str(featureDefinition); + + for j in range(layer.GetFeatureCount()): + feature = layer.GetNextFeature() + geometry = feature.GetGeometryRef() + + fields = {} + + for k in range(fieldCount-1): + #fields[ fieldNames[k] ] = feature.GetRawFieldRef(k) + fields[ fieldNames[k] ] = feature.GetFieldAsString(k) + if attributeStats: + try: + attributeStatsTable[ fieldNames[k] ][ feature.GetFieldAsString(k) ] = attributeStatsTable[ fieldNames[k] ][ feature.GetFieldAsString(k) ] + 1 + except: + attributeStatsTable[ fieldNames[k] ].update({ feature.GetFieldAsString(k) : 1}) + + + # Translate attributes into tags, as defined per the selected translation method + tags = translateAttributes(fields) + + if debugTags: + print + print tags + + # Do the reprojection (or pass if no reprojection is neccesary, see the lambda function definition) + reproject(geometry) + + # Now we got the fields for this feature. Now, let's convert the geometry. + # Points will get converted into nodes. + # LineStrings will get converted into a set of ways, each having only two nodes + # Polygons will be converted into relations + # Later, we'll fix the topology and simplify the ways. If a relation can be simplified into a way (i.e. only has one member), it will be. Adjacent segments will be merged if they share tags and direction. + + # We'll split a geometry into subGeometries or "elementary" geometries: points, linestrings, and polygons. This will take care of OGRMultiLineStrings, OGRGeometryCollections and the like + + geometryType = geometry.GetGeometryType() + + subGeometries = [] + + if geometryType == wkbPoint or geometryType == wkbLineString or geometryType == wkbPolygon: + subGeometries = [geometry] + elif geometryType == wkbMultiPoint or geometryType == wkbMultiLineString or geometryType == wkbMultiPolygon or geometryType == wkbGeometryCollection: + if showProgress: sys.stdout.write('M') + for k in range(geometry.GetGeometryCount()): + subGeometries.append(geometry.GetGeometryRef(k)) + + elif geometryType == wkbPoint25D or geometryType == wkbLineString25D or geometryType == wkbPolygon25D: + if showProgress: sys.stdout.write('z') + subGeometries = [geometry] + elif geometryType == wkbMultiPoint25D or geometryType == wkbMultiLineString25D or geometryType == wkbMultiPolygon25D or geometryType == wkbGeometryCollection25D: + if showProgress: sys.stdout.write('Mz') + for k in range(geometry.GetGeometryCount()): + subGeometries.append(geometry.GetGeometryRef(k)) + + elif geometryType == wkbUnknown: + print "Geometry type is wkbUnknown, feature will be ignored\n" + elif geometryType == wkbNone: + print "Geometry type is wkbNone, feature will be ignored\n" + else: + print "Unknown or unimplemented geometry type :" + str(geometryType) + ", feature will be ignored\n" + + + for geometry in subGeometries: + if geometry.GetDimension() == 0: + # 0-D = point + if showProgress: sys.stdout.write(',') + x = geometry.GetX() + y = geometry.GetY() + + nodeID = addNode(x,y,tags) + # TODO: tags + + elif geometry.GetDimension() == 1: + # 1-D = linestring + if showProgress: sys.stdout.write('|') + + lineID = elementIdCounter + elementIdCounter = elementIdCounter - 1 + lineSegments[lineID] = lineStringToSegments(geometry,lineID) + lineTags[lineID] = tags + lineCount = lineCount + 1 + + elif geometry.GetDimension() == 2: + # FIXME + # 2-D = area + + if showProgress: sys.stdout.write('O') + areaID = elementIdCounter + elementIdCounter = elementIdCounter - 1 + rings = [] + + for k in range(0,geometry.GetGeometryCount()): + if showProgress: sys.stdout.write('r') + rings.append(lineStringToSegments(geometry.GetGeometryRef(k), areaID)) + + areaRings[areaID] = rings + areaTags[areaID] = tags + areaCount = areaCount + 1 + # TODO: tags + # The ring 0 will be the outer hull, any other rings will be inner hulls. + + + +print +print "Nodes: " + str(nodeCount) +print "Way segments: " + str(segmentCount) +print "Lines: " + str(lineCount) +print "Areas: " + str(areaCount) + +print +print "Joining segments" + + +# OK, all features should be parsed in the arrays by now +# Let's start to do some topological magic + +# We'll iterate through all the lines and areas, then iterate through all the nodes contained there +# We'll then fetch all segments referencing that node. If a pair of segments share the same references (i.e. they are part of the same line or area), they will be joined as one and de-referenced from that node. Joining segments mean than the concept of segment changes at this point, becoming linestrings or ways. +# There are some edge cases in which the algorithm may not prove optimal: if a line (or area ring) crosses itself, then the node will have more than two segments referenced to the line (or area), and does NOT check for the optimal one. As a result, lines that cross themselves may be (incorrectly) split into two and merged via a relation. In other words, the order of the points in a line (or ring) may not be kept if the line crosses itself. +# The algorithm will not check if the node has been de-referenced: instead, it will check for the first and last node of the segments involved - if the segments have already been joined, the check will fail. + + + + +def simplifyNode(nodeID): + global nodeRefs, segmentNodes, segmentRefs, showProgress, lineSegments, areaRings, segmentJoinCount + #for (nodeID, segments) in nodeRefs.items(): + segments = nodeRefs[nodeID] + + segmentsJoined = 0 + #print + #print "Node ID: " + str(nodeID) + #print "Node references to: " + str(segments) + + # We have to try all pairs of segments somehow + for segmentID1 in segments.copy(): + for segmentID2 in segments.copy(): # We'll be changing the references, so make sure we iterate through the original list + if segmentID1 != segmentID2: + #print str(segmentID1) + " vs " + str(segmentID2) + try: + if segmentNodes[segmentID1][-1] == segmentNodes[segmentID2][0] == nodeID and segmentRefs[segmentID1] == segmentRefs[segmentID2] : + + #print "Segment " + str(segmentID1) + ": " + str(segmentNodes[segmentID1]) + #print "Segment " + str(segmentID2) + ": " + str(segmentNodes[segmentID2]) + + #if showProgress: sys.stdout.write('=') + segmentNodes[segmentID1].extend( segmentNodes[segmentID2][1:] ) # Voila! Joined! + for nodeShifted in segmentNodes[segmentID2][1:]: # Replace node references + #print "deleting reference from node " + str(nodeShifted) + " to segment " + str(segmentID2) + "; updating to " + str(segmentID1) + del nodeRefs[nodeShifted][segmentID2] + nodeRefs[nodeShifted].update({segmentID1:True}) + + # TODO: Check for potential clashes between the references? As in "way X has these segments in the wrong direction". The trivial case for this looks like a topology error, anyway. + # Anyway, delete all references to the second segment - we're 100% sure that the line or area references the first one 'cause we've checked before joining the segments + for segmentRef in segmentRefs[segmentID2]: + try: + lineSegments[segmentRef].remove(segmentID2) + except: + for ring in areaRings[segmentRef]: + try: + ring.remove(segmentID2) + except: + pass + + + del segmentRefs[segmentID2] + + del segmentNodes[segmentID2] + segmentJoinCount = segmentJoinCount +1 + segmentsJoined = segmentsJoined + 1 + except: + pass # This is due to the node no longer referencing to a segment because we just de-referenced it in a previous pass of the loop; this will be quite common + + # FIXME: if segmentsJoined > 1, this should mark the node for further testing - It's very likely to be a self-intersection. + + if showProgress: sys.stdout.write(str(segmentsJoined)) + +print +print "Simplifying line segments" +for line in lineSegments.values(): + #print line + for segmentID in line: # No need to check the last segment, it could not be simplyfied + #print segmentID + #print segmentNodes[segmentID] + for nodeID in segmentNodes[segmentID]: + simplifyNode(nodeID) + #simplifyNode(segmentNodes[segmentID][0]) # last node in segment + +print +print "Simplifying area segments" +for area in areaRings.values(): + for ring in area: + for segmentID in ring: + for nodeID in segmentNodes[segmentID]: + simplifyNode(nodeID) # last node in segment + + +# That *should* do it... but a second pass through all the nodes will really fix things up. I wonder why some nodes are left out of the previous pass +print +print "Simplifying remaining nodes" +for node in nodeRefs.keys(): + simplifyNode(node) + + +print +print "Nodes: " + str(nodeCount) +print "Original way segments: " + str(segmentCount) +print "Segment join operations: " + str(segmentJoinCount) +print "Lines: " + str(lineCount) +print "Areas: " + str(areaCount) + +#print nodeRefs +#print segmentNodes +#print lineSegments +#print areaRings +#print segmentRefs + + + +print +print "Generating OSM XML..." +print "Generating nodes." + + +#w = XMLWriter(sys.stdout) +w = XMLWriter(open(outputFile,'w')) + +w.start("osm", version='0.6', generator='ogr2osm') + +# First, the nodes +for (nodeID,(x,y)) in nodeCoords.items(): + w.start("node", visible="true", id=str(nodeID), lat=str(y), lon=str(x)) + for (tagKey,tagValue) in nodeTags[nodeID].items(): + if tagValue: + w.element("tag", k=tagKey, v=tagValue) + w.end("node") + if showProgress: sys.stdout.write('.') + + +#print "Generated nodes. On to shared segments." + +# Now, the segments used by more than one line/area, as untagged ways + + +#for (segmentID, segmentRef) in segmentRefs.items(): + #if len(segmentRef) > 1: + #print "FIXME: output shared segment" + #outputtedSegments[segmentID] = True + + +print +print "Generated nodes. On to lines." + +# Next, the lines, either as ways or as relations + +outputtedSegments = {} + +for (lineID, lineSegment) in lineSegments.items(): + if showProgress: sys.stdout.write(str(len(lineSegment)) + " ") + if len(lineSegment) == 1: # The line will be a simple way + w.start('way', id=str(lineID), action='modify', visible='true') + + for nodeID in segmentNodes[ lineSegment[0] ]: + w.element('nd',ref=str(nodeID)) + + for (tagKey,tagValue) in lineTags[lineID].items(): + if tagValue: + w.element("tag", k=tagKey, v=tagValue) + + w.end('way') + pass + else: # The line will be a relationship + #print + #print "Line ID " + str(lineID) + " uses more than one segment: " + str(lineSegment) + for segmentID in lineSegment: + if segmentID not in outputtedSegments: + w.start('way', id=str(segmentID), action='modify', visible='true') + for nodeID in segmentNodes[ segmentID ]: + w.element('nd',ref=str(nodeID)) + w.end('way') + w.start('relation', id=str(lineID), action='modify', visible='true') + for segmentID in lineSegment: + w.element('member', type='way', ref=str(segmentID), role='') + for (tagKey,tagValue) in lineTags[lineID].items(): + if tagValue: + w.element("tag", k=tagKey, v=tagValue) + w.end('relation') + +print +print "Generated lines. On to areas." + +# And last, the areas, either as ways or as relations + +#print areaRings + +for (areaID, areaRing) in areaRings.items(): + #sys.stdout.write(str(len(areaRings))) + + if len(areaRing) == 1 and len(areaRing[0]) == 1: # The area will be a simple way + w.start('way', id=str(areaID), action='modify', visible='true') + + for nodeID in segmentNodes[ areaRing[0][0] ]: + w.element('nd',ref=str(nodeID)) + + for (tagKey,tagValue) in areaTags[areaID].items(): + if tagValue: + w.element("tag", k=tagKey, v=tagValue) + + w.end('way') + if showProgress: sys.stdout.write('0 ') + else: + segmentsUsed = 0 + segmentsUsedInRing = 0 + #print "FIXME" + + for ring in areaRing: + for segmentID in ring: + if segmentID not in outputtedSegments: + w.start('way', id=str(segmentID), action='modify', visible='true') + for nodeID in segmentNodes[ segmentID ]: + w.element('nd',ref=str(nodeID)) + w.end('way') + + + w.start('relation', id=str(areaID), action='modify', visible='true') + w.element("tag", k='type', v='multipolygon') + + role = 'outer' + for ring in areaRing: + for segmentID in ring: + w.element('member', type='way', ref=str(segmentID), role=role) + segmentsUsed = segmentsUsed + 1 + segmentsUsedInRing = segmentsUsedInRing + 1 + role = 'inner' + #if showProgress: sys.stdout.write(str(segmentsUsedInRing)+'r') + segmentsUsedInRing = 0 + + for (tagKey,tagValue) in areaTags[areaID].items(): + if tagValue: + w.element("tag", k=tagKey, v=tagValue) + w.end('relation') + if showProgress: sys.stdout.write(str(segmentsUsed) + " ") + + + + +if attributeStats: + print + for (attribute, stats) in attributeStatsTable.items(): + print "All values for attribute " + attribute + ":" + print stats + + +print +print "All done. Enjoy your data!" + + +w.end("osm") + + diff --git a/lib/ogr2osm/translations/carreteras_gv.py b/lib/ogr2osm/translations/carreteras_gv.py new file mode 100644 index 00000000..1cb2a2dc --- /dev/null +++ b/lib/ogr2osm/translations/carreteras_gv.py @@ -0,0 +1,76 @@ +""" +Translation rules for Valencia Community road network. + +This is so simple that it'll be used as an example of how to build translation methods. + +The important thing is that a file like this must define a "translateAttributes" function that, taking a directory of attributes (key:value), will return a directory of tags (key:value) + +The function must allow for empty directories. + +The reason for using a full module is that you can define auxiliary functions and data structures here. But this example is so simple it won't have any auxiliary stuff at all. +""" + + + +def translateAttributes(attrs): + if not attrs: return + + tags = {} + + # Use the "NOM_ACT" attribute as the name= tag + if attrs['NOM_ACT']: + tags = {'name':attrs['NOM_ACT']} + + # If the name contains an hyphen, set it to the ref= tag too + if attrs['NOM_ACT'].find('-') != -1: + tags.update({'ref':attrs['NOM_ACT']}) + + + + # Depending on the value of the TIPUS_ACT, set the highway= tag + if attrs['TIPUS_ACT'] == 'Altres comunitats autonomes': + tags.update({'highway':'road'}) + + elif attrs['TIPUS_ACT'] == 'Basica': + tags.update({'highway':'trunk'}) + + elif attrs['TIPUS_ACT'] == 'En construccio': + tags.update({'highway':'construction','construction':'road'}) + + elif attrs['TIPUS_ACT'] == 'Via de servei': + tags.update({'highway':'service'}) + + elif attrs['TIPUS_ACT'] == 'Municipal': + tags.update({'highway':'primary'}) + + elif attrs['TIPUS_ACT'] == 'Autopista/Autovia': + tags.update({'highway':'motorway'}) + + elif attrs['TIPUS_ACT'] == 'Auxiliar': + tags.update({'highway':'motorway_link'}) + + elif attrs['TIPUS_ACT'] == 'Local': + tags.update({'highway':'tertiary'}) + + elif attrs['TIPUS_ACT'] == 'Fora de servei': + tags.update({'highway':'road', 'access':'no'}) + + + + #print "foo!" + return tags + #sys.exit() + + + + +""" +Taken from --attribute-stats: + +All values for attribute TIPUS_ACT: +{'Altres comunitats autonomes': 224, 'Basica': 2950, 'En construccio': 360, 'Via de servei': 505, 'Municipal': 3135, 'Autopista/Autovia': 2849, 'Auxiliar': 9887, 'Local': 4868, 'Fora de servei': 35} + +All values for attribute TIT_ACT: +{'Diputacio': 3337, 'Municipal': 2152, 'Sense determinar': 6498, 'Ministeri': 5908, 'Conselleria': 6881, 'Fora de servei': 35, 'Altres administracions': 2} +""" + diff --git a/lib/ogr2osm/translations/ithaca_haiti.py b/lib/ogr2osm/translations/ithaca_haiti.py new file mode 100644 index 00000000..42f880b9 --- /dev/null +++ b/lib/ogr2osm/translations/ithaca_haiti.py @@ -0,0 +1,55 @@ +""" +Translation rules for the Ithaca Haiti damage assesment report data. + +""" + + + +def translateAttributes(attrs): + if not attrs: return + + tags = {} + + tags.update({'FIXME':'Check for duplicated data'}) + + tags.update({'source':'Ithaca'}) + + tags.update({'source:imagery': attrs['SOURCE'] }) + + # Only thing to translate is the "TYPE" attr. + + if attrs['TYPE'] == 'Landslide': + tags.update({'earthquake:damage':'landslide'}) + + + if attrs['TYPE'] == 'Damaged infrastructure': + tags.update({'earthquake:damage':'damaged_infrastructure'}) + + + if attrs['TYPE'] == 'Spontaneous camp': + tags.update({'tourism':'camp_site'}) + tags.update({'refugee':'yes'}) + tags.update({'earthquake:damage':'spontaneous_camp'}) + + + if attrs['TYPE'] == 'Collapsed building': + tags.update({'earthquake:damage':'collapsed_buiding'}) + tags.update({'building':'collapsed'}) + + + + + #print "foo!" + return tags + #sys.exit() + + + + +""" +Taken from --attribute-stats: + +All values for attribute TYPE: +{'Landslide': 45, 'Damaged infrastructure': 35, 'Spontaneous camp': 87, 'Collapsed building': 1490} + +""" \ No newline at end of file diff --git a/test/fixtures/warps.yml b/test/fixtures/warps.yml new file mode 100644 index 00000000..075af8a4 --- /dev/null +++ b/test/fixtures/warps.yml @@ -0,0 +1,19 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +one: + parent_id: 1 + content_type: MyString + filename: MyString + thumbnail: MyString + size: 1 + width: 1 + height: 1 + +two: + parent_id: 1 + content_type: MyString + filename: MyString + thumbnail: MyString + size: 1 + width: 1 + height: 1 diff --git a/test/unit/warp_test.rb b/test/unit/warp_test.rb new file mode 100644 index 00000000..456b0765 --- /dev/null +++ b/test/unit/warp_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class WarpTest < ActiveSupport::TestCase + # Replace this with your real tests. + test "the truth" do + assert true + end +end