citysdk 0.1.2.5 → 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +33 -0
- data/Gemfile.lock +27 -21
- data/README.md +55 -33
- data/bin/csdk +1220 -0
- data/citysdk.gemspec +6 -5
- data/examples/simple_api.rb +68 -0
- data/lib/citysdk/api.rb +105 -105
- data/lib/citysdk/file_reader.rb +195 -120
- data/lib/citysdk/importer.rb +36 -113
- data/lib/citysdk/util.rb +26 -4
- data/lib/citysdk.rb +1 -1
- data/spec/api_spec.rb +41 -34
- data/spec/import_spec.rb +40 -75
- metadata +40 -24
- data/b.sh +0 -4
data/lib/citysdk/file_reader.rb
CHANGED
@@ -7,12 +7,11 @@ require 'tmpdir'
|
|
7
7
|
|
8
8
|
module CitySDK
|
9
9
|
|
10
|
-
|
11
10
|
class FileReader
|
12
11
|
|
13
|
-
RE_Y = /lat|(y.*coord)|(y.*loc(atie|ation)?)/i
|
14
|
-
RE_X = /lon|lng|(x.*coord)|(x.*loc(atie|ation)?)/i
|
15
|
-
RE_GEO = /^geom(etry)?|location|locatie$/i
|
12
|
+
RE_Y = /lat|(y.*coord)|(y.*pos.*)|(y.*loc(atie|ation)?)/i
|
13
|
+
RE_X = /lon|lng|(x.*coord)|(x.*pos.*)|(x.*loc(atie|ation)?)/i
|
14
|
+
RE_GEO = /^(geom(etry)?|location|locatie|coords|coordinates)$/i
|
16
15
|
RE_NAME = /(title|titel|naam|name)/i
|
17
16
|
RE_A_NAME = /^(naam|name|title|titel)$/i
|
18
17
|
|
@@ -41,68 +40,74 @@ module CitySDK
|
|
41
40
|
end
|
42
41
|
end
|
43
42
|
|
44
|
-
|
45
43
|
@params[:rowcount] = @content.length
|
46
44
|
getFields unless @params[:fields]
|
47
45
|
guessName unless @params[:name]
|
48
46
|
guessSRID unless @params[:srid]
|
49
47
|
findUniqueField unless @params[:unique_id]
|
50
48
|
getAddress unless @params[:hasaddress]
|
51
|
-
|
49
|
+
setId_Name
|
52
50
|
end
|
53
51
|
|
54
52
|
def getAddress
|
55
53
|
pd = pc = hn = ad = false
|
54
|
+
@params[:housenumber] = nil
|
55
|
+
@params[:hasaddress] = 'unknown'
|
56
|
+
@params[:postcode] = nil
|
56
57
|
@params[:fields].reverse.each do |f|
|
57
|
-
pd = f if ( f.to_s =~ /postcode|post/i )
|
58
58
|
pc = f if ( f.to_s =~ /^(post|zip|postal)code$/i )
|
59
59
|
hn = f if ( f.to_s =~ /huisnummer|housenumber|(house|huis)(nr|no)|number/i)
|
60
60
|
ad = f if ( f.to_s =~ /address|street|straat|adres/i)
|
61
61
|
end
|
62
|
-
|
63
|
-
|
64
|
-
if pc
|
65
|
-
@params[:hasaddress] = 'certain'
|
66
|
-
@params[:postcode] = pc
|
67
|
-
elsif pd
|
68
|
-
@params[:hasaddress] = 'maybe'
|
69
|
-
@params[:postcode] = pd
|
70
|
-
end
|
71
|
-
@params[:housenumber] = hn ? hn : ad
|
62
|
+
if pc and (ad or hn)
|
63
|
+
@params[:hasaddress] = 'certain'
|
72
64
|
end
|
73
|
-
|
74
|
-
|
65
|
+
@params[:postcode] = pc
|
66
|
+
@params[:housenumber] = hn ? hn : ad
|
75
67
|
|
68
|
+
end
|
76
69
|
|
70
|
+
def setId_Name
|
71
|
+
count = 123456
|
72
|
+
if @params[:unique_id]
|
73
|
+
@content.each do |h|
|
74
|
+
h[:properties][:id] = h[:properties][:data][@params[:unique_id]]
|
75
|
+
h[:properties][:title] = h[:properties][:data][@params[:name]] if @params[:name]
|
76
|
+
end
|
77
|
+
else
|
78
|
+
@params[:unique_id] = :csdk_gen
|
79
|
+
# @params[:fields] << :csdk_gen
|
80
|
+
# @params[:original_fields] << :csdk_gen
|
81
|
+
# @params[:alternate_fields][:csdk_gen] = :csdk_gen
|
82
|
+
@content.each do |h|
|
83
|
+
h[:properties][:id] = "cg_#{count}"
|
84
|
+
h[:properties][:title] = h[:properties][:data][@params[:name]] if @params[:name]
|
85
|
+
count += 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
77
90
|
def findUniqueField
|
78
91
|
fields = {}
|
79
|
-
|
80
|
-
|
81
|
-
return if @content[0][:id]
|
82
|
-
|
92
|
+
@params[:unique_id] = nil
|
83
93
|
@content.each do |h|
|
84
|
-
h[:properties].each do |k,v|
|
85
|
-
fields[k] =
|
86
|
-
|
94
|
+
h[:properties][:data].each do |k,v|
|
95
|
+
fields[k] = Hash.new(0) if fields[k].nil?
|
96
|
+
fields[k][v] += 1
|
87
97
|
end
|
88
98
|
end
|
89
99
|
|
90
100
|
fields.each_key do |k|
|
91
101
|
if fields[k].length == @params[:rowcount]
|
92
|
-
@params[:unique_id] =
|
102
|
+
@params[:unique_id] = k
|
93
103
|
break
|
94
104
|
end
|
95
105
|
end
|
96
|
-
|
97
|
-
if unfield
|
98
|
-
@content.each do |h|
|
99
|
-
h[:id] = h[:properties][unfield]
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
106
|
+
|
103
107
|
end
|
104
108
|
|
105
109
|
def guessName
|
110
|
+
@params[:name] = nil
|
106
111
|
@params[:fields].reverse.each do |k|
|
107
112
|
if(k.to_s =~ RE_A_NAME)
|
108
113
|
@params[:name] = k
|
@@ -116,13 +121,18 @@ module CitySDK
|
|
116
121
|
|
117
122
|
def getFields
|
118
123
|
@params[:fields] = []
|
119
|
-
@
|
120
|
-
|
124
|
+
@params[:original_fields] = []
|
125
|
+
@params[:alternate_fields] = {}
|
126
|
+
@content[0][:properties][:data].each_key do |k|
|
127
|
+
k = (k.to_sym rescue k) || k
|
128
|
+
@params[:fields] << k
|
129
|
+
@params[:original_fields] << k
|
130
|
+
@params[:alternate_fields][k] = k
|
121
131
|
end
|
122
132
|
end
|
123
133
|
|
124
134
|
def guessSRID
|
125
|
-
return
|
135
|
+
return unless @content[0][:geometry] and @content[0][:geometry].class == Hash
|
126
136
|
@params[:srid] = 4326
|
127
137
|
g = @content[0][:geometry][:coordinates]
|
128
138
|
if(g)
|
@@ -133,11 +143,13 @@ module CitySDK
|
|
133
143
|
lat = g[1]
|
134
144
|
# if lon > -180.0 and lon < 180.0 and lat > -90.0 and lat < 90.0
|
135
145
|
# @params[:srid] = 4326
|
136
|
-
#
|
137
|
-
if lon
|
146
|
+
# else
|
147
|
+
if lon.between?(-7000.0,300000.0) and lat.between?(289000.0,629000.0)
|
138
148
|
# Dutch new rd system
|
139
149
|
@params[:srid] = 28992
|
140
150
|
end
|
151
|
+
else
|
152
|
+
|
141
153
|
end
|
142
154
|
end
|
143
155
|
|
@@ -157,18 +169,36 @@ module CitySDK
|
|
157
169
|
p.parse(s)
|
158
170
|
g = f.geometry
|
159
171
|
return g.srid,g.as_json[:type],g
|
160
|
-
rescue
|
172
|
+
rescue => e
|
173
|
+
end
|
174
|
+
nil
|
175
|
+
end
|
176
|
+
|
177
|
+
def isWktGeometry(s)
|
178
|
+
begin
|
179
|
+
f = GeoRuby::SimpleFeatures::GeometryFactory::new
|
180
|
+
p = GeoRuby::SimpleFeatures::EWKTParser.new(f)
|
181
|
+
p.parse(s)
|
182
|
+
g = f.geometry
|
183
|
+
return g.srid,g.as_json[:type],g
|
184
|
+
rescue => e
|
161
185
|
end
|
162
186
|
nil
|
163
187
|
end
|
164
188
|
|
189
|
+
GEOMETRIES = ["point", "multipoint", "linestring", "multilinestring", "polygon", "multipolygon"]
|
165
190
|
def isGeoJSON(s)
|
191
|
+
return nil if s.class != Hash
|
166
192
|
begin
|
167
|
-
if
|
193
|
+
if GEOMETRIES.include?(s[:type].downcase)
|
168
194
|
srid = 4326
|
169
|
-
if s[:crs]
|
170
|
-
|
171
|
-
|
195
|
+
if s[:crs] and s[:crs][:properties]
|
196
|
+
if s[:crs][:type] == 'OGC'
|
197
|
+
urn = s[:crs][:properties][:urn].split(':')
|
198
|
+
srid = urn.last.to_i if (urn[4] == 'EPSG')
|
199
|
+
elsif s[:crs][:type] == 'EPSG'
|
200
|
+
srid = s[:crs][:properties][:code]
|
201
|
+
end
|
172
202
|
end
|
173
203
|
return srid,s[:type],s
|
174
204
|
end
|
@@ -176,98 +206,151 @@ module CitySDK
|
|
176
206
|
end
|
177
207
|
nil
|
178
208
|
end
|
209
|
+
|
210
|
+
def geomFromText(coords)
|
211
|
+
# begin
|
212
|
+
# a = factory.parse_wkt(coords)
|
213
|
+
# rescue
|
214
|
+
# end
|
215
|
+
|
216
|
+
if coords =~ /^(\w+)(.+)/
|
217
|
+
if GEOMETRIES.include?($1.downcase)
|
218
|
+
type = $1.capitalize
|
219
|
+
coor = $2.gsub('(','[').gsub(')',']')
|
220
|
+
coor = coor.gsub(/([-+]?[0-9]*\.?[0-9]+)\s+([-+]?[0-9]*\.?[0-9]+)/) { "[#{$1},#{$2}]" }
|
221
|
+
coor = JSON.parse(coor)
|
222
|
+
return { :type => type,
|
223
|
+
:coordinates => coor }
|
224
|
+
end
|
225
|
+
end
|
226
|
+
{}
|
227
|
+
end
|
179
228
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
229
|
+
def findGeometry(xfield=nil, yfield=nil)
|
230
|
+
unless(xfield and yfield)
|
231
|
+
@params[:hasgeometry] = nil
|
232
|
+
xs = true
|
233
|
+
ys = true
|
234
|
+
|
235
|
+
@content[0][:properties][:data].each do |k,v|
|
236
|
+
next if k.nil?
|
237
|
+
|
238
|
+
if k.to_s =~ RE_GEO
|
239
|
+
|
240
|
+
srid,g_type = isWkbGeometry(v)
|
241
|
+
if(srid)
|
242
|
+
@params[:srid] = srid
|
243
|
+
@params[:geomtry_type] = g_type
|
244
|
+
@content.each do |h|
|
245
|
+
a,b,g = isWkbGeometry(h[:properties][:data][k])
|
246
|
+
h[:geometry] = g
|
247
|
+
h[:properties][:data].delete(k)
|
248
|
+
end
|
249
|
+
@params[:hasgeometry] = k
|
250
|
+
return true
|
195
251
|
end
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
252
|
+
|
253
|
+
|
254
|
+
srid,g_type = isWktGeometry(v)
|
255
|
+
if(srid)
|
256
|
+
@params[:srid] = srid
|
257
|
+
@params[:geomtry_type] = g_type
|
258
|
+
@content.each do |h|
|
259
|
+
a,b,g = isWktGeometry(h[:properties][:data][k])
|
260
|
+
h[:geometry] = g
|
261
|
+
h[:properties][:data].delete(k)
|
262
|
+
end
|
263
|
+
@params[:hasgeometry] = k
|
264
|
+
return true
|
207
265
|
end
|
208
|
-
|
209
|
-
|
266
|
+
|
267
|
+
srid,g_type = isGeoJSON(v)
|
268
|
+
if(srid)
|
269
|
+
@params[:srid] = srid
|
270
|
+
@params[:geomtry_type] = g_type
|
271
|
+
@content.each do |h|
|
272
|
+
h[:geometry] = h[:properties][:data][k]
|
273
|
+
h[:properties].delete(k)
|
274
|
+
end
|
275
|
+
@params[:hasgeometry] = k
|
276
|
+
return true
|
277
|
+
end
|
278
|
+
|
210
279
|
end
|
211
280
|
|
212
|
-
|
213
|
-
|
214
|
-
|
281
|
+
hdc = k.to_s.downcase
|
282
|
+
if hdc == 'longitude' or hdc == 'lon' or hdc == 'x'
|
283
|
+
xfield=k; xs=false
|
215
284
|
end
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
if hdc == 'longitude' or hdc == 'lon'
|
222
|
-
xfield=k; xs=false
|
223
|
-
end
|
224
|
-
if hdc == 'latitude' or hdc == 'lat'
|
225
|
-
yfield=k; ys=false
|
285
|
+
if hdc == 'latitude' or hdc == 'lat' or hdc == 'y'
|
286
|
+
yfield=k; ys=false
|
287
|
+
end
|
288
|
+
xfield = k if xs and (hdc =~ RE_X)
|
289
|
+
yfield = k if ys and (hdc =~ RE_Y)
|
226
290
|
end
|
227
|
-
xfield = k if xs and (hdc =~ RE_X)
|
228
|
-
yfield = k if ys and (hdc =~ RE_Y)
|
229
291
|
end
|
230
292
|
|
231
293
|
if xfield and yfield and (xfield != yfield)
|
232
|
-
@params[:hasgeometry] =
|
294
|
+
@params[:hasgeometry] = [xfield,yfield].to_s
|
233
295
|
@content.each do |h|
|
234
|
-
h[:geometry] = {:type => 'Point', :coordinates => [h[:properties][xfield].gsub(',','.').to_f, h[:properties][yfield].gsub(',','.').to_f]}
|
235
|
-
h[:properties].delete(yfield)
|
236
|
-
h[:properties].delete(xfield)
|
296
|
+
h[:geometry] = {:type => 'Point', :coordinates => [h[:properties][:data][xfield].gsub(',','.').to_f, h[:properties][:data][yfield].gsub(',','.').to_f]}
|
297
|
+
h[:properties][:data].delete(yfield)
|
298
|
+
h[:properties][:data].delete(xfield)
|
237
299
|
end
|
238
300
|
@params[:geomtry_type] = 'Point'
|
301
|
+
@params[:fields].delete(xfield) if @params[:fields]
|
302
|
+
@params[:fields].delete(yfield) if @params[:fields]
|
303
|
+
return true
|
304
|
+
elsif (xfield and yfield)
|
305
|
+
# factory = ::RGeo::Cartesian.preferred_factory()
|
306
|
+
@params[:hasgeometry] = "[#{xfield}]"
|
307
|
+
@content.each do |h|
|
308
|
+
h[:geometry] = geomFromText(h[:properties][:data][xfield])
|
309
|
+
h[:properties][:data].delete(xfield) if h[:geometry]
|
310
|
+
end
|
311
|
+
@params[:geomtry_type] = ''
|
312
|
+
@params[:fields].delete(xfield) if @params[:fields]
|
239
313
|
return true
|
240
314
|
end
|
315
|
+
|
316
|
+
|
241
317
|
false
|
242
318
|
end
|
243
319
|
|
244
|
-
|
245
320
|
def readCsv(path)
|
246
321
|
@file = path
|
247
322
|
c=''
|
248
323
|
File.open(path, "r:bom|utf-8") do |fd|
|
249
324
|
c = fd.read
|
250
325
|
end
|
251
|
-
|
326
|
+
unless @params[:utf8_fixed]
|
252
327
|
detect = CharlockHolmes::EncodingDetector.detect(c)
|
253
328
|
c = CharlockHolmes::Converter.convert(c, detect[:encoding], 'UTF-8') if detect
|
254
329
|
end
|
255
330
|
c = c.force_encoding('utf-8')
|
331
|
+
c = c.gsub(/\r\n?/, "\n")
|
256
332
|
@content = []
|
257
|
-
@params[:colsep] = findColSep(StringIO.new(c))
|
333
|
+
@params[:colsep] = findColSep(StringIO.new(c)) unless @params[:colsep]
|
258
334
|
csv = CSV.new(c, :col_sep => @params[:colsep], :headers => true, :skip_blanks =>true)
|
259
|
-
csv.
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
335
|
+
csv.header_convert { |h| h.blank? ? '_' : h.strip.gsub(/\s+/,'_') }
|
336
|
+
csv.convert { |h| h ? h.strip : '' }
|
337
|
+
index = 0
|
338
|
+
begin
|
339
|
+
csv.each do |row|
|
340
|
+
r = row.to_hash
|
341
|
+
h = {}
|
342
|
+
r.each do |k,v|
|
343
|
+
h[(k.to_sym rescue k) || k] = v
|
344
|
+
end
|
345
|
+
@content << {properties: {data: h} }
|
346
|
+
index += 1
|
264
347
|
end
|
265
|
-
|
348
|
+
rescue => e
|
349
|
+
raise CitySDK::Exception.new("Read CSV; line #{index}; #{e.message}")
|
266
350
|
end
|
267
351
|
findGeometry
|
268
352
|
end
|
269
353
|
|
270
|
-
|
271
354
|
def readJSON(path)
|
272
355
|
@content = []
|
273
356
|
@file = path
|
@@ -278,20 +361,23 @@ module CitySDK
|
|
278
361
|
hash = CitySDK::parseJson(raw)
|
279
362
|
|
280
363
|
if hash.is_a?(Hash) and hash[:type] and (hash[:type] == 'FeatureCollection')
|
364
|
+
# GeoJSON
|
281
365
|
hash[:features].each do |f|
|
282
366
|
f.delete(:type)
|
367
|
+
f[:properties] = {data: f[:properties]}
|
283
368
|
@content << f
|
284
369
|
end
|
285
|
-
@params[:hasgeometry] = '
|
286
|
-
findUniqueField if @content[0][:id].nil?
|
370
|
+
@params[:hasgeometry] = 'GeoJSON'
|
287
371
|
else
|
372
|
+
# Free-form JSON
|
288
373
|
val,length = nil,0
|
289
|
-
|
290
374
|
if hash.is_a?(Array)
|
375
|
+
# one big array
|
291
376
|
val,length = hash,hash.length
|
292
377
|
else
|
293
378
|
hash.each do |k,v|
|
294
379
|
if v.is_a?(Array)
|
380
|
+
# the longest array value in the Object
|
295
381
|
val,length = v,v.length if v.length > length
|
296
382
|
end
|
297
383
|
end
|
@@ -299,19 +385,18 @@ module CitySDK
|
|
299
385
|
|
300
386
|
if val
|
301
387
|
val.each do |h|
|
302
|
-
@content << {:properties => h}
|
388
|
+
@content << { :properties => {:data => h} }
|
303
389
|
end
|
304
390
|
end
|
305
391
|
findGeometry
|
306
392
|
end
|
307
393
|
end
|
308
394
|
|
309
|
-
|
310
395
|
def sridFromPrj(str)
|
311
396
|
begin
|
312
397
|
connection = Faraday.new :url => "http://prj2epsg.org"
|
313
398
|
resp = connection.get('/search.json', {:mode => 'wkt', :terms => str})
|
314
|
-
if resp.status
|
399
|
+
if resp.status.between?(200, 299)
|
315
400
|
resp = CitySDK::parseJson resp.body
|
316
401
|
@params[:srid] = resp[:codes][0][:code].to_i
|
317
402
|
end
|
@@ -328,19 +413,19 @@ module CitySDK
|
|
328
413
|
prj = File.exists?(prj) ? File.read(prj) : nil
|
329
414
|
sridFromPrj(prj) if (prj and @params[:srid].nil?)
|
330
415
|
|
331
|
-
@params[:hasgeometry] = '
|
416
|
+
@params[:hasgeometry] = 'ESRI Shape'
|
332
417
|
|
333
418
|
|
334
419
|
GeoRuby::Shp4r::ShpFile.open(path) do |shp|
|
335
420
|
shp.each do |shape|
|
336
421
|
h = {}
|
337
422
|
h[:geometry] = CitySDK::parseJson(shape.geometry.to_json) #a GeoRuby SimpleFeature
|
338
|
-
h[:properties] = {}
|
423
|
+
h[:properties] = {:data => {}}
|
339
424
|
att_data = shape.data #a Hash
|
340
425
|
shp.fields.each do |field|
|
341
426
|
s = att_data[field.name]
|
342
427
|
s = s.force_encoding('ISO8859-1') if s.class == String
|
343
|
-
h[:properties][field.name.to_sym] = s
|
428
|
+
h[:properties][:data][field.name.to_sym] = s
|
344
429
|
end
|
345
430
|
@content << h
|
346
431
|
end
|
@@ -398,13 +483,3 @@ module CitySDK
|
|
398
483
|
|
399
484
|
end
|
400
485
|
|
401
|
-
|
402
|
-
|
403
|
-
# {
|
404
|
-
# :headers => ['aap','noot','titel', 'gid', 'geometry']
|
405
|
-
# :config => {:geom => 'geometry', :name => 'titel', :id => 'gid', :srid => 28892}
|
406
|
-
# :data => [
|
407
|
-
# ['jpo','pipo','naam1','1092',{:type => 'Point', :coordinates => [5.3, 52.4]}],
|
408
|
-
# ['jpa','popi','naam2','1093',{:type => 'Point', :coordinates => [5.1, 52.1]}]
|
409
|
-
# ]
|
410
|
-
# }
|