citysdk 0.1.2.5 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
data/bin/csdk ADDED
@@ -0,0 +1,1220 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require "yaml"
7
+
8
+ # require 'citysdk'
9
+ require_relative '../lib/citysdk.rb'
10
+ include CitySDK
11
+ require "curses"
12
+ include Curses
13
+
14
+
15
+ $categories = %w{ none geography natural cultural civic tourism mobility administrative environment health education security commercial }
16
+ $outheight = 0
17
+ $outwidth = 0
18
+
19
+ ############# convenience ####################################################
20
+
21
+
22
+
23
+ class Array
24
+ def sharedStart
25
+ a = self.sort
26
+ w1 = a[0]
27
+ w2 = a[-1]
28
+ l = w1.length
29
+ i = 0
30
+ while(i < l and w1[i] == w2[i])
31
+ i += 1
32
+ end
33
+ w1[0...i]
34
+ end
35
+ end
36
+
37
+ ############# menus ##########################################################
38
+
39
+ class MenuItem
40
+ attr_accessor :t, :c, :k
41
+ def initialize(key,text,command,*args)
42
+ @k = key
43
+ @t = text
44
+ @c = command
45
+ @a = args.flatten
46
+ end
47
+ def call
48
+ (@a.length > 0) ? @c.call(@a) : @c.call
49
+ end
50
+ end
51
+
52
+ $currentMenu = nil
53
+
54
+ class Menu
55
+ def initialize(items)
56
+ @items = items
57
+ end
58
+
59
+ def replace(n,i)
60
+ @items[n]=i
61
+ end
62
+
63
+ def run(outwinproc = nil)
64
+ $currentMenu = self
65
+ $vpos = 3
66
+ banner
67
+ @items.each do |i|
68
+ setpos($vpos,1)
69
+ addstr(" #{i.k}: #{i.t}")
70
+ $vpos+=1
71
+ end
72
+ $vpos+=1
73
+ setpos($vpos,1)
74
+ addstr("please select")
75
+ setpos($vpos+1,0)
76
+ addstr("-" * Curses.cols)
77
+
78
+ $outwin.close if $outwin
79
+ $outheight = Curses.lines - $vpos - 3
80
+ $outwidth = Curses.cols
81
+ $outwin=Curses::Window.new( $outheight, $outwidth, $vpos+2, 1 )
82
+ $outwin.refresh
83
+ outwinproc.call if outwinproc
84
+
85
+ curs_set(0)
86
+ while true
87
+ noecho
88
+ c = getch
89
+ @items.each do |i|
90
+ if i.k == c
91
+ sherror('')
92
+ $outwin.clear
93
+ i.call
94
+ setpos($vpos,1)
95
+ clrtoeol
96
+ addstr("please select")
97
+ refresh
98
+ break
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+
106
+ ############# layers #########################################################
107
+
108
+
109
+
110
+ def layerSummary
111
+ layer = selectLayer("please select layer",false)
112
+ return unless layer
113
+ $outwin.clear
114
+ outAddstr 1,sprintf("%15s: #{layer[:name]}", 'name')
115
+ json = $api.get("/layers/#{layer[:name]}/objects?per_page=1&count")
116
+ outAddstr 2, sprintf("%15s: #{$api.last_result[:headers]['x-result-count']}", 'data frames')
117
+ outAddstr 3, sprintf("%15s: #{layer[:title]}", 'title')
118
+ outAddstr 4, sprintf("%15s: #{layer[:category]}/#{layer[:subcategory]}", 'category')
119
+ outAddstr 5, sprintf("%15s: #{layer[:description]}", 'description')
120
+ outAddstr 6, sprintf("%15s: #{layer[:licence]}", 'licence')
121
+ outAddstr 7, sprintf("%15s: #{layer[:rdf_type]}", 'type')
122
+ outAddstr 8, sprintf("%15s: #{layer[:fields].blank? ? 'none defined' : layer[:fields].map {|f| f[:name]}.join(', ')}", 'fields')
123
+ end
124
+
125
+
126
+ def createLayer
127
+ return unless $user
128
+
129
+ userdomains = $user[:domains].split(",")
130
+ userdomains << 'test'
131
+ userdomains = userdomains.map{|d|d.strip}.uniq
132
+ layer = {}
133
+
134
+ $outwin.clear
135
+ $outwin.setpos(1,0)
136
+ $outwin.addstr "create new layer:"
137
+ $outwin.refresh
138
+
139
+ return cancelled unless get_input("name",:name,layer,3)
140
+ while true do
141
+ l = $layers.select { |i| (i[:name] == layer[:name]) }
142
+ break if l.blank?
143
+ $outwin.setpos(1,0)
144
+ $outwin.addstr "name needs to bve unique:"
145
+ $outwin.refresh
146
+ layer[:name] = ''
147
+ return cancelled unless get_input("name",:name,layer,3)
148
+ end
149
+ if !$user[:admin]
150
+ while true do
151
+ a = layer[:name].split(".")
152
+ break if a.length > 1 and userdomains.include?(a[0])
153
+ $outwin.setpos(1,0)
154
+ $outwin.addstr "name needs to start with one of #{userdomains}:"
155
+ $outwin.refresh
156
+ layer[:name] = ''
157
+ return cancelled unless get_input("name",:name,layer,3)
158
+ end
159
+ end
160
+
161
+ return cancelled unless get_input("title",:title,layer,4)
162
+ return cancelled unless get_input("description",:description,layer,5)
163
+ return cancelled unless get_input("licence",:licence,layer,6)
164
+ return cancelled unless get_input("rdf_type",:rdf_type,layer,7)
165
+ ret = self.get_string_array("please enter data sources, empty line ends input.",8)
166
+ layer[:data_sources] = ret.blank? ? [""] : ret
167
+
168
+ c = get_input_from_list("category:", $user[:admin] ? $categories: $categories[1..-1], 8)
169
+ layer[:category] = c if c.length
170
+ return cancelled unless get_input("subcategory",:subcategory,layer,9)
171
+
172
+ if $user[:admin]
173
+ c = get_input_from_list("owner", $users.map {|u| u[:name]} , 10)
174
+ else
175
+ c = $user[:name]
176
+ end
177
+ layer[:owner] = c
178
+
179
+ if $user[:admin]
180
+ return cancelled unless get_input("authoritative",:authoritative,layer,11)
181
+ layer[:authoritative] = isTrue?(layer[:authoritative])
182
+ end
183
+
184
+ return cancelled unless get_input("webservice_url",:webservice_url,layer,11)
185
+ return cancelled unless get_input("update_rate",:update_rate,layer,12)
186
+
187
+ # '@context', 'fields'
188
+
189
+ $outwin.setpos(10,0)
190
+ $outwin.addstr JSON.pretty_generate(layer) + "\n\n"
191
+ $outwin.refresh
192
+
193
+ if yesNo?("is this correct? ")
194
+ apiCall('updating layer') do
195
+ json = ''
196
+ if $api.authenticate($credentials_hash[:login],$credentials_hash[:password])
197
+ begin
198
+ json = $api.post "/layers", layer
199
+ getLayersList
200
+ $layer = $layers.select { |i| (i[:name] == layer[:name]) }[0]
201
+ rescue => ex
202
+ json = ex.message
203
+ end
204
+ $api.release
205
+ outMessage("Layer created: '#{$layer[:name]}'..")
206
+ return $layer
207
+ end
208
+ end
209
+ end
210
+ nil
211
+ end
212
+
213
+
214
+ def selectLayer(prompt,owner=true)
215
+ while true
216
+ n = get_input_from_list(prompt, $layers.map {|u| u[:name]}, 1)
217
+ return nil if n.blank?
218
+ l = $layers.select { |i| (i[:name] == n) }
219
+ if l.blank?
220
+ outAddstr(3,"Non-existant layer: #{n}")
221
+ elsif owner and !owned?(l[0])
222
+ outAddstr(3,"Layer not owned by #{$user[:fullname]}; #{n}")
223
+ else
224
+ return l[0]
225
+ end
226
+ end
227
+ end
228
+
229
+
230
+ def layerAddFields
231
+ return unless $layer and owned?($layer)
232
+ $outwin.clear; v = 2
233
+ outAddstr 1, "current fields:"
234
+
235
+
236
+ end
237
+
238
+ def clearLayer
239
+ layer = selectLayer("please select layer to empty",true)
240
+ return unless layer
241
+ $outwin.clear
242
+ if yesNo?("clear all objects from #{layer[:name]}; sure?", 1)
243
+ apiCall("deleting from layer #{layer[:name]}", "all #{layer[:name]} objects deleted") do
244
+ if $api.authenticate($credentials_hash[:login],$credentials_hash[:password])
245
+ $api.delete("/layers/#{layer[:name]}/objects")
246
+ $api.release
247
+ ''
248
+ end
249
+ end
250
+ end
251
+ end
252
+
253
+ def selectNewOrExistingLayer
254
+ $outwin.clear
255
+ $outwin.setpos(1,0)
256
+ $outwin.addstr "Set layer: make new or use existing layer? (n/e)"
257
+ $outwin.refresh
258
+ a = charIn(['n','N','e','E'])
259
+ return cancelled if a == 27
260
+ if a.downcase == 'n'
261
+ return createLayer
262
+ end
263
+ return selectLayer("please select layer",true)
264
+ end
265
+
266
+
267
+
268
+ def doImport
269
+
270
+ return unless $user
271
+
272
+ if $file_hash[:layername].blank?
273
+ $file_hash[:layername] = $layer = selectNewOrExistingLayer
274
+ return unless $layer
275
+ end
276
+
277
+ unless ($user[:admin] or $layer[:owner][:name] == $user[:name])
278
+ outMessage("User has no access to layer #{$layer[:name]}..")
279
+ return
280
+ end
281
+
282
+ $file_hash[:batch_size] == 250 if $file_hash[:batch_size].blank?
283
+ get_input("Please select batch size for upload:", :batch_size, $file_hash, 1)
284
+
285
+ if $file_hash[:hasgeometry].nil? and $file_hash[:postcode] and $file_hash[:housenumber]
286
+ outAddstr 1,"No geometry detected, but possibly address and postcode. "
287
+ outAddstr 2,"We can possibly link to postcode/housenumber combination."
288
+ addreslayer = get_input_from_list("Please select address layer (blank for cancel)", $layers.map {|u| u[:name]}, 3)
289
+ return cancelled if addreslayer.blank?
290
+ alayer = $layers.select {|u| addreslayer = u[:name]}
291
+ if alayer.blank?
292
+ outMessage("No such layer: #{addreslayer}..")
293
+ return
294
+ end
295
+ field = get_input_from_list("Please pc/hn field (postcode_huisnummer)", $layers.map {|u| u[:name]}, 2)
296
+ end
297
+
298
+ if $file_hash[:hasgeometry]
299
+ n = $file_reader.content.length
300
+ i = 0
301
+ begin
302
+ $file_hash[:layername] = $layer[:name]
303
+ imp = Importer.new($file_hash, $file_reader)
304
+ imp.api.batch_size = $file_hash[:batch_size].to_i
305
+ n = imp.doImport do |d|
306
+ i += 1
307
+ outAddstr 1,"importing: #{Integer(100 * i / n)}%" if(i % 100)
308
+ end
309
+ outMessage("Import of #{$file_hash[:file_path]} succesful; processed #{n[:created]} items.")
310
+ rescue Exception => e
311
+ $file_hash[:layername] = nil
312
+ sherror(e.message)
313
+ return
314
+ end
315
+ end
316
+
317
+ if $layer[:fields].blank?
318
+ if $api.authenticate($credentials_hash[:login],$credentials_hash[:password])
319
+ $file_hash[:fields].each do |f|
320
+ k = $file_hash[:alternate_fields][f]
321
+ field = {name: k.to_s}
322
+ $api.post("/layers/#{$layer[:name]}/fields", field)
323
+ $layer[:fields] << field
324
+ end
325
+ $api.release
326
+ end
327
+ end
328
+
329
+ end
330
+
331
+ def owned?(layer)
332
+ return true if $user[:admin]
333
+ return ($user[:name] == layer[:owner][:name]) if layer.class == Hash
334
+ $layers.each do |l|
335
+ return true if l[:name]==layer and ($user[:name] == l[:owner][:name])
336
+ end
337
+ false
338
+ end
339
+
340
+
341
+
342
+ def editLayer
343
+
344
+ l = selectLayer("layer to edit")
345
+ return if l.blank?
346
+ layer = l.deep_copy
347
+
348
+ $outwin.clear
349
+ $outwin.setpos(1,0)
350
+ $outwin.addstr "edit layer #{layer[:name]}:"
351
+ $outwin.refresh
352
+
353
+ return cancelled unless get_input("title",:title,layer,3)
354
+ return cancelled unless get_input("description",:description,layer,4)
355
+ return cancelled unless get_input("licence",:licence,layer,5)
356
+ return cancelled unless get_input("rdf_type",:rdf_type,layer,6)
357
+ ret = self.get_string_array("please enter data sources, empty line ends input.",7)
358
+ layer[:data_sources] = ret.blank? ? [""] : ret
359
+
360
+ c = get_input_from_list("category (#{layer[:category]})", $user[:admin] ? $categories: $categories[1..-1], 7)
361
+ layer[:category] = c if c.length
362
+ return cancelled unless get_input("subcategory",:subcategory,layer,8)
363
+
364
+ if $user[:admin]
365
+ c = get_input_from_list("owner (#{layer[:owner][:name]})", $users.map {|u| u[:name]} , 9)
366
+ layer[:owner] = c if c.length
367
+ end
368
+
369
+ if $user[:admin]
370
+ return cancelled unless get_input("authoritative",:authoritative,layer,10)
371
+ layer[:authoritative] = isTrue?(layer[:authoritative])
372
+ end
373
+
374
+ return cancelled unless get_input("webservice_url",:webservice_url,layer,11)
375
+ return cancelled unless get_input("update_rate",:update_rate,layer,12)
376
+
377
+ # '@context', 'fields'
378
+
379
+ layer.delete_if{ |k, v| (v == l[k]) or v.blank? }
380
+
381
+ if layer.empty?
382
+ $outwin.clear
383
+ $outwin.setpos(1,0)
384
+ $outwin.addstr "No change.."
385
+ $outwin.refresh
386
+ else
387
+
388
+ $outwin.setpos(10,0)
389
+ $outwin.addstr JSON.pretty_generate(layer) + "\n\n"
390
+ $outwin.refresh
391
+
392
+ $outwin.setpos(11,0)
393
+ if yesNo?("is this correct? ")
394
+ apiCall('updating layer') do
395
+ json = ''
396
+ if $api.authenticate($credentials_hash[:login],$credentials_hash[:password])
397
+ begin
398
+ json = $api.patch "/layers/#{layer[:name]}", layer
399
+ getLayersList
400
+ json = $layers.select { |i| (i[:name] == layer[:name]) }[0]
401
+ rescue Exception => ex
402
+ json = ex.message
403
+ end
404
+ $api.release
405
+ end
406
+ json
407
+ end
408
+ end
409
+ end
410
+
411
+ end
412
+
413
+
414
+ def deleteLayer
415
+ return unless $user
416
+ layer = selectLayer("layer to delete")
417
+ return if layer.blank?
418
+
419
+ if yesNo?("Are you sure to delete layer #{layer[:name]}?",1)
420
+ apiCall('deleting layer',"Layer #{layer[:name]} is deleted.") do
421
+ json = ''
422
+ if $api.authenticate($credentials_hash[:login],$credentials_hash[:password])
423
+ json = $api.delete "/layers/#{layer[:name]}"
424
+ $api.release
425
+ getLayersList
426
+ end
427
+ json
428
+ end
429
+ end
430
+ end
431
+
432
+
433
+ def getLayersList
434
+ begin
435
+ $layers = $api.get('/layers?per_page=1000')
436
+ $layers = $layers[:features].map { |f| f[:properties]}
437
+ rescue Exception => e
438
+ end
439
+ end
440
+
441
+ def showLayers
442
+ i = 1
443
+ $layers.each do |u|
444
+ s = sprintf("%-15s ",u[:name])
445
+ s += sprintf("#{u[:category]}/#{u[:subcategory]}, #{u[:owner][:fullname]}, #{u[:description]}")
446
+ $outwin.setpos(i,0)
447
+ $outwin.addstr s.remove_non_ascii
448
+ i += 1
449
+ end
450
+ $outwin.refresh
451
+ end
452
+
453
+
454
+ ############# users ##########################################################
455
+
456
+ def getUserList
457
+ $users = []
458
+ begin
459
+ $api.authenticate($credentials_hash[:login],$credentials_hash[:password]) if $user
460
+ $users = $api.get('/owners?per_page=1000')
461
+ $api.release if $user
462
+ rescue Exception => e
463
+ sherror("getUserList: " + e.message)
464
+ end
465
+ end
466
+
467
+ def showUsers
468
+ i = 2
469
+ $users.each do |u|
470
+ s = u[:admin] ? 'a ' : ' '
471
+ s += sprintf("%-12s ",u[:name]) if $user[:admin]
472
+ s += sprintf("%-20s #{u[:email]}", u[:fullname])
473
+ s += " (#{u[:domains]})"
474
+ $outwin.setpos(i,0)
475
+ $outwin.addstr s.remove_non_ascii
476
+ i += 1
477
+ end
478
+ $outwin.refresh
479
+ end
480
+
481
+
482
+
483
+ def deleteUser(u)
484
+ return unless ($user and $user[:admin])
485
+ user = selectUser("user to delete")
486
+ return if user.nil?
487
+ if (u[:name] =~ /citysdk/i)
488
+ return sherror("User 'citysdk' cannot be deleted.")
489
+ end
490
+ if yesNo?("Are you sure; deleting user #{u[:name]}?",1)
491
+ apiCall('deleting user',"User #{u[:name]} is deleted.") do
492
+ json = ''
493
+ if $api.authenticate($credentials_hash[:login],$credentials_hash[:password])
494
+ json = $api.delete "/owners/#{u[:name]}"
495
+ $api.release
496
+ getUserList
497
+ end
498
+ json
499
+ end
500
+ end
501
+ end
502
+
503
+
504
+ def createUser
505
+ if $user and $user[:admin]
506
+ user = {}
507
+ $outwin.clear
508
+ $outwin.setpos(1,0)
509
+ $outwin.addstr "create new user:"
510
+ $outwin.refresh
511
+ user[:admin] = 'y/n'
512
+
513
+ return cancelled unless get_input("login name",:name,user,3)
514
+ return cancelled if user[:name].blank?
515
+ return cancelled unless get_input("full name",:fullname,user,4)
516
+ return cancelled unless get_input("email",:email,user,5)
517
+ return cancelled unless get_input("organization",:organization,user,6)
518
+ return cancelled unless get_input("website",:website,user,7)
519
+ return cancelled unless get_input("domains",:domains,user,8,'test, ')
520
+ return cancelled unless get_input("admin",:admin,user,9)
521
+ return cancelled unless get_input("password",:password,user,10)
522
+
523
+ user[:admin] = isTrue?(user[:admin])
524
+ user[:domains] = user[:domains].split(",").map {|d| d.strip }.join(",")
525
+
526
+ apiCall('add new user') do
527
+ json = ''
528
+ if $api.authenticate($credentials_hash[:login],$credentials_hash[:password])
529
+ begin
530
+ json = $api.post "/owners", user
531
+ getUserList
532
+ rescue => ex
533
+ json = ex.message
534
+ end
535
+ $api.release
536
+ end
537
+ json
538
+ end
539
+ end
540
+ end
541
+
542
+
543
+ def selectUser(prompt)
544
+ return $user unless $user[:admin]
545
+ n = get_input_from_list(prompt, $users.map {|u| u[:name]}, 1)
546
+ l = $users.select { |i| (i[:name] == n) }
547
+ if l.blank?
548
+ sherror("Non-existant user: #{n}")
549
+ return nil
550
+ end
551
+ l[0]
552
+ end
553
+
554
+
555
+
556
+ def editUser
557
+ return unless $user
558
+ u = selectUser("user to edit")
559
+ return if u.nil?
560
+ user = u.deep_copy
561
+ $outwin.clear
562
+ $outwin.setpos(1,0)
563
+ $outwin.addstr "edit user #{user[:name]}:"
564
+ $outwin.refresh
565
+
566
+ vp = 3
567
+
568
+ return cancelled unless get_input("full name",:fullname,user,vp+=1)
569
+ return cancelled unless get_input("email",:email,user,vp+=1)
570
+ return cancelled unless get_input("organization",:organization,user,vp+=1)
571
+ return cancelled unless get_input("website",:website,user,vp+=1)
572
+ return cancelled unless get_input("domains",:domains,user,vp+=1) if $user[:admin]
573
+ return cancelled unless get_input("password",:password,user,vp+=1)
574
+ if $user[:admin]
575
+ return cancelled unless get_input("admin",:admin,user,vp+=1)
576
+ user[:admin] = isTrue?(user[:admin])
577
+ end
578
+
579
+ user.delete_if{ |k, v| (v == u[k]) or v.blank? }
580
+ user[:domains] = user[:domains].split(",").map {|d| d.strip }.join(",") if user[:domains]
581
+
582
+ if user.empty?
583
+ $outwin.clear
584
+ $outwin.setpos(1,0)
585
+ $outwin.addstr "No change.."
586
+ $outwin.refresh
587
+ else
588
+
589
+ $outwin.setpos(vp+=2,0)
590
+ $outwin.addstr JSON.pretty_generate(user) + "\n\n"
591
+ $outwin.refresh
592
+
593
+ if yesNo?("is this correct? ")
594
+ apiCall('updating user') do
595
+ json = ''
596
+ if $api.authenticate($credentials_hash[:login],$credentials_hash[:password])
597
+ begin
598
+ json = $api.patch "/owners/#{u[:name]}", user
599
+ getUserList
600
+ rescue => ex
601
+ json = ex.message
602
+ end
603
+ $api.release
604
+ end
605
+ json
606
+ end
607
+ end
608
+
609
+ end
610
+
611
+ end
612
+
613
+
614
+ ############# files ##########################################################
615
+ def loadConfig
616
+ f = $file_hash[:file_path].gsub(/\.\w+$/,'.cfg')
617
+ if File.exists?(f)
618
+ naf = {}
619
+ $file_hash = JSON.parse(File.read(f), symbolize_names: true)
620
+ $file_hash[:password] = $credentials_hash[:password]
621
+ $file_hash[:host] = $credentials_hash[:host]
622
+ $file_hash[:login] = $credentials_hash[:login]
623
+ $file_hash[:fields].map! { |f| (f.to_sym rescue f) || f }
624
+ $file_hash[:original_fields].map! { |f| (f.to_sym rescue f) || f }
625
+ $file_hash[:alternate_fields].each { |k,v| naf[(k.to_sym rescue k) || k] = v }
626
+ $file_hash[:alternate_fields] = naf
627
+ $file_reader.setId_Name
628
+ # log JSON.pretty_generate($file_hash)
629
+ end
630
+ end
631
+
632
+
633
+ def saveConfig
634
+ return unless $file_reader
635
+ f = $file_hash[:file_path].gsub(/\.\w+$/,'.cfg')
636
+ File.open(f,"w") do |fd|
637
+ fh = $file_hash.deep_copy
638
+ fh[:password] = '***'
639
+ fd.write(JSON.pretty_generate(fh))
640
+ end
641
+ end
642
+
643
+
644
+ def saveFile
645
+ return unless $file_reader
646
+ r = {}
647
+ started = false
648
+ return cancelled unless get_input("Please enter file name",:fn,r,1)
649
+ if r[:fn]
650
+ f = File.expand_path(r[:fn])
651
+ File.open(f,"w") do |fd|
652
+ fd.write('{"type": "FeatureCollection", "features": ['+"\n")
653
+ $file_reader.content.each do |o|
654
+ fd.write(",") if started
655
+ started = true
656
+ fd.write(formatObject(o).to_json)
657
+ fd.write("\n")
658
+ end
659
+ fd.write('] }')
660
+ end
661
+ end
662
+ end
663
+
664
+
665
+ def formatObject(o)
666
+ begin
667
+ tp = {}
668
+ # log JSON.pretty_generate($file_hash)
669
+ o[:properties][:data].each do |k,v|
670
+ if $file_hash[:fields].include?(k)
671
+ k2 = $file_hash[:alternate_fields][k]
672
+ k2 = k if k2.blank?
673
+ tp[k2] = v
674
+ end
675
+ end
676
+ o[:properties][:data] = tp
677
+
678
+ if $file_hash[:srid] and $file_hash[:srid] != 4326
679
+ o[:crs] = {
680
+ type: 'EPSG',
681
+ properties: {
682
+ code: $file_hash[:srid]
683
+ }
684
+ }
685
+ end
686
+ rescue => e
687
+ sherror("Error: #{e.message}")
688
+ end
689
+ o
690
+ end
691
+
692
+ def showSample
693
+ return unless $file_reader
694
+ begin
695
+ o = $file_reader.content[rand($file_reader.content.length)].deep_copy
696
+ # log JSON.pretty_generate(o)
697
+ if o[:geometry] and o[:geometry].class == Hash
698
+ o[:geometry][:coordinates] = ['...'] if o[:geometry][:coordinates] and o[:geometry][:type] != 'Point'
699
+ else
700
+ o[:geometry] = nil
701
+ end
702
+ $outwin.clear
703
+ $outwin.setpos(1,0)
704
+ $outwin.addstr formatObject(o).to_yaml # JSON.pretty_generate(formatObject(o))
705
+ $outwin.refresh
706
+ rescue => e
707
+ sherror("Error: #{e.message}")
708
+ end
709
+ end
710
+
711
+
712
+ def fileSummary(vp)
713
+
714
+ $outwin.setpos(vp+=1,0)
715
+ $outwin.addstr sprintf("%14s #{$file_hash[:file_path]}", "file:")
716
+
717
+ $outwin.setpos(vp+=1,0)
718
+ $outwin.addstr sprintf("%14s #{$file_hash[:rowcount]}", "total rows:")
719
+
720
+ $outwin.setpos(vp+=1,0)
721
+ $outwin.addstr sprintf("%14s #{$file_hash[:unique_id].to_s}","unique id:")
722
+
723
+ if $file_hash[:postcode]
724
+ $outwin.setpos(vp+=1,0)
725
+ $outwin.addstr sprintf("%14s #{$file_hash[:postcode]}","postcode in:")
726
+ end
727
+
728
+ if $file_hash[:housenumber]
729
+ $outwin.setpos(vp+=1,0)
730
+ $outwin.addstr sprintf("%14s #{$file_hash[:housenumber]}","address/number in:")
731
+ end
732
+
733
+ if $file_hash[:hasgeometry]
734
+ $outwin.setpos(vp+=1,0)
735
+ $outwin.addstr sprintf("%14s found in %s; srid: #{$file_hash[:srid]}","geometry:",$file_hash[:hasgeometry])
736
+ end
737
+ vp
738
+ end
739
+
740
+ def loadFile
741
+ $file_reader = nil
742
+ return cancelled unless get_input("file path",:file_path,$credentials_hash ,1)
743
+ begin
744
+ $file_hash = $file_hash.merge $credentials_hash.dup
745
+ $file_hash[:file_path] = File.expand_path($file_hash[:file_path])
746
+ $file_reader = FileReader.new($file_hash)
747
+ $file_hash[:layername] = $layer[:name] if $layer
748
+ fileSummary(0)
749
+ $outwin.refresh
750
+ rescue => e
751
+ sherror("Error reading file: #{e.inspect}")
752
+ end
753
+
754
+ end
755
+
756
+ def editFields
757
+ props = $file_reader.content[rand($file_reader.content.length)][:properties][:data]
758
+
759
+ $file_hash[:fields] = []
760
+ $file_hash[:alternate_fields] = {}
761
+ $file_hash[:original_fields].each do |f|
762
+ $file_hash[:fields] << f
763
+ end
764
+
765
+ $outwin.clear
766
+ outAddstr(1, "For each field, please choose Accept, Rename or Ignore")
767
+ vp = 3
768
+ accepted_fields = []
769
+ accepted_fields << $file_hash[:unique_id]
770
+ curs_set(2)
771
+ $file_hash[:fields].each do |f|
772
+ outAddstr(vp,sprintf("-- sample: #{props[f]} --"))
773
+ field = getFieldHeader(vp+2,0,sprintf("Field: '#{f}'; (a/r/i) "),f)
774
+ return cancelled if field == 27
775
+ if field
776
+ accepted_fields << f
777
+ $file_hash[:alternate_fields][f] = field
778
+ end
779
+ end
780
+ curs_set(0)
781
+ $file_hash[:fields] = accepted_fields.uniq
782
+ $file_reader.guessName
783
+ $file_reader.getAddress
784
+ $outwin.clear
785
+ fileSummary(1)
786
+ $outwin.refresh
787
+ end
788
+
789
+
790
+ ############# utils ##########################################################
791
+
792
+
793
+ def assignGeometry
794
+ list = $file_hash[:fields].map {|f| $file_hash[:alternate_fields][f].to_s}
795
+ $outwin.clear
796
+ x = get_input_from_list('please select x (longitude) field: ', list , 3)
797
+ y = get_input_from_list(' select y (latitude) field: ', list , 3)
798
+ unless (x.blank? and y.blank?)
799
+ $file_reader.findGeometry(x.to_sym, y.to_sym)
800
+ # $file_reader.guessSRID
801
+ end
802
+ $outwin.clear
803
+ fileSummary(1)
804
+ $outwin.refresh
805
+ end
806
+
807
+ def assignAddress
808
+ list = $layer[:fields].map {|f| f[:name]}
809
+ $outwin.clear
810
+ x = get_input_from_list('please select x (longitude) field: ', list , 3)
811
+ y = get_input_from_list(' select y (latitude) field: ', list , 3)
812
+ unless (x.blank? and y.blank?)
813
+ end
814
+ end
815
+
816
+
817
+
818
+ def getFieldHeader(v,h,s,f)
819
+ a = ''
820
+ $outwin.setpos(v,h)
821
+ $outwin.clrtoeol
822
+ $outwin.addstr s
823
+ $outwin.refresh
824
+
825
+ a = charIn(["A","a","R","r","I","i"])
826
+ return 27 if a.ord == 27
827
+
828
+ case a.downcase
829
+ when 'a'
830
+ return f
831
+ when 'i'
832
+ return nil
833
+ when 'r'
834
+ $outwin.setpos(v,h)
835
+ $outwin.clrtoeol
836
+ $outwin.addstr "Replacement name for field '#{f}': "
837
+ $outwin.refresh
838
+ echo
839
+ s = $outwin.getstr
840
+ noecho
841
+ s
842
+ end
843
+ end
844
+
845
+
846
+ def isTrue?(o)
847
+ return true if
848
+ (o == true) or
849
+ (o =~ /^y$/i) or
850
+ (o =~ /^t$/i) or
851
+ (o =~ /^true$/i) or
852
+ (o =~ /^yes$/i) or
853
+ (o =~ /^j$/i) or
854
+ (o =~ /^ja$/i)
855
+ false
856
+ end
857
+
858
+ def cancelled
859
+ outMessage("Cancelled...")
860
+ end
861
+
862
+
863
+ def doExit
864
+ File.open($configFile, "w") do |f|
865
+ f.write($credentials_hash.to_json)
866
+ f.write("\n")
867
+ end
868
+ close_screen
869
+ exit!(0)
870
+ end
871
+
872
+
873
+ def doConnect
874
+ begin
875
+ if $api.nil?
876
+ return if ($credentials_hash[:host].nil? or $credentials_hash[:host]=~/^\s*$/)
877
+ $api = API.new($credentials_hash[:host])
878
+ end
879
+ if $user.nil?
880
+ if $credentials_hash[:login] and $credentials_hash[:login]!~/^\s*$/
881
+ if $credentials_hash[:password] and $credentials_hash[:password]!~/^\s*$/
882
+ res = $api.authenticate($credentials_hash[:login],$credentials_hash[:password])
883
+ $user = $api.get '/owners/' + $credentials_hash[:login]
884
+ $api.release
885
+ end
886
+ end
887
+ end
888
+ rescue Exception => e
889
+ sherror("Error connecting to host: #{e.message}")
890
+ end
891
+ if $api
892
+ getUserList
893
+ getLayersList
894
+ end
895
+ end
896
+
897
+
898
+ def setHost
899
+ vp = 1
900
+ $api = $user = $error = nil
901
+ return cancelled unless get_input("hostname",:host,$credentials_hash,1)
902
+ return cancelled unless get_input("user name",:login,$credentials_hash,2)
903
+ return cancelled unless get_input("password",:password,$credentials_hash,3)
904
+ doConnect
905
+ runFromTop
906
+ end
907
+
908
+
909
+ def showMatches(l,v)
910
+ $outwin.clear
911
+ l.each do |i|
912
+ $outwin.setpos(v+=1,0)
913
+ $outwin.addstr("- #{i}")
914
+ end
915
+ $outwin.refresh
916
+ end
917
+
918
+ def get_input_from_list(prompt, list, vp, res = '')
919
+ # 259 up
920
+ # 258 dn
921
+ udindex = -1
922
+ matchl = list
923
+ prv = inp = nil
924
+ curs_set(1)
925
+ $outwin.keypad(true)
926
+ while true
927
+ $outwin.setpos(vp,0)
928
+ $outwin.clrtoeol
929
+ $outwin.addstr("#{prompt} -> #{res}")
930
+ $outwin.refresh
931
+ prv = inp
932
+ inp = $outwin.getch
933
+
934
+ case inp.ord
935
+
936
+ when 259 # up
937
+ if udindex == -1
938
+ matchl = list.select { |i| i.starts_with?(res) }
939
+ udindex = 0
940
+ end
941
+ res = matchl[udindex -= 1] if (udindex > 0)
942
+ when 258 # down
943
+ if udindex == -1
944
+ matchl = list.select { |i| i.starts_with?(res) }
945
+ udindex = 0
946
+ end
947
+ res = matchl[udindex += 1] if (udindex < matchl.length-1)
948
+ when 27
949
+ outMessage("Cancelled")
950
+ $outwin.keypad(false)
951
+ return ''
952
+ when 9
953
+ if prv.ord == 9
954
+ showMatches(matchl,vp+1)
955
+ else
956
+ matchl = list.select { |i| i.starts_with?(res)}
957
+ res = matchl.sharedStart if matchl[0]
958
+ udindex = -1
959
+ end
960
+ when 10,13
961
+ break
962
+ when 127,8
963
+ udindex = -1
964
+ res = res[0...-1]
965
+ else
966
+ udindex = -1
967
+ res << inp if (inp.ord > 31)
968
+ end
969
+ end
970
+ $outwin.keypad(false)
971
+ curs_set(0)
972
+ res
973
+ end
974
+
975
+
976
+ def self.get_string_array(prompt,vp)
977
+ ret = []
978
+ echo
979
+ tempwin=$outwin.subwin( $outheight - vp - 2, $outwidth-2, $vpos+vp+2, 2 )
980
+ tempwin.setpos(1,1)
981
+ tempwin.addstr(prompt)
982
+ vp = 2
983
+ tempwin.setpos(vp,1)
984
+ loop do
985
+ tempwin.setpos(vp,1)
986
+ tempwin.addstr("-> ")
987
+ tempwin.refresh
988
+ s = tempwin.getstr
989
+ break if s.length == 0
990
+ vp += 1
991
+ ret << s
992
+ end
993
+ noecho
994
+ tempwin.clear
995
+ tempwin.close
996
+ $outwin.refresh
997
+ ret
998
+ end
999
+
1000
+
1001
+ def get_input(prompt, symbol, hash, vp, defs=nil)
1002
+ res = defs ? defs : ''
1003
+ echo
1004
+ curs_set(1)
1005
+ $outwin.keypad(true)
1006
+
1007
+ while true
1008
+ if hash[symbol] and symbol != :password
1009
+ outAddstr(vp,"#{prompt} (#{hash[symbol]}) -> #{res}")
1010
+ else
1011
+ outAddstr(vp,"#{prompt} -> #{res}")
1012
+ end
1013
+ $outwin.refresh
1014
+ inp = $outwin.getch
1015
+ case inp.ord
1016
+ when 27
1017
+ outMessage("Cancelled")
1018
+ return false
1019
+ when 10,13
1020
+ break
1021
+ when 127,8
1022
+ res = res[0...-1]
1023
+ else
1024
+ res << inp if (inp.ord > 31)
1025
+ end
1026
+ end
1027
+
1028
+ curs_set(0)
1029
+ noecho
1030
+ hash[symbol] = res.strip if res.length > 0
1031
+ $outwin.keypad(false)
1032
+ true
1033
+ end
1034
+
1035
+
1036
+
1037
+ def banner
1038
+ clear
1039
+ setpos(0,0)
1040
+ addstr "CitySDK API, interactive console. "
1041
+ setpos(1,0)
1042
+ addstr "host: #{$credentials_hash[:host]}" if $api
1043
+ addstr "; user: #{$credentials_hash[:login]}" if $user
1044
+ addstr "; layer: #{$layer[:name]} " if $layer
1045
+ end
1046
+
1047
+ def runFromTop
1048
+ if ($user and $user[:admin])
1049
+ $topMenu.replace(2, MenuItem.new('3','users', lambda{$userMenu.run}))
1050
+ else
1051
+ $topMenu.replace(2, MenuItem.new('3','edit user', lambda{editUser()}))
1052
+ end
1053
+ $topMenu.run
1054
+ end
1055
+
1056
+ def yesNo?(p,v=nil)
1057
+ $outwin.setpos(v,0) if v
1058
+ $outwin.addstr("#{p} ")
1059
+ $outwin.refresh
1060
+ ['Y','y','j','J'].include? $outwin.getch
1061
+ end
1062
+
1063
+
1064
+
1065
+ def apiCall(error,noerror='',&block)
1066
+ begin
1067
+ json = yield
1068
+ $outwin.clear
1069
+ $outwin.setpos(1,0)
1070
+ if json.class == Hash
1071
+ $outwin.addstr JSON.pretty_generate(json) + "\n"
1072
+ elsif json == ''
1073
+ $outwin.addstr noerror + "\n"
1074
+ elsif json.class == Array
1075
+ if json[0] == {}
1076
+ $outwin.addstr noerror + "\n"
1077
+ else
1078
+ $outwin.addstr JSON.pretty_generate(json[0]) + "\n"
1079
+ end
1080
+ end
1081
+ $outwin.refresh
1082
+ rescue Exception => e
1083
+ sherror("Error #{error}: #{e.message}")
1084
+ end
1085
+ end
1086
+
1087
+ def outAddstr(v,s)
1088
+ $outwin.setpos(v,0)
1089
+ $outwin.clrtoeol
1090
+ $outwin.addstr(s)
1091
+ $outwin.refresh
1092
+ end
1093
+
1094
+
1095
+ def sherror(e)
1096
+ setpos(Curses.lines-1,1)
1097
+ clrtoeol
1098
+ addstr($error=e)
1099
+ refresh
1100
+ log $error unless $error.blank?
1101
+ false
1102
+ end
1103
+
1104
+ def log(m)
1105
+ File.open(File.expand_path('~/csdk.log'), "a") do |f|
1106
+ f.write("#{m}\n")
1107
+ end
1108
+ end
1109
+
1110
+
1111
+ def not_yet
1112
+ outMessage("Not yet implemented")
1113
+ end
1114
+
1115
+ def charIn(arr)
1116
+ a = $outwin.getch
1117
+ while !(arr.include?(a) or a.ord == 27)
1118
+ a = $outwin.getch
1119
+ end
1120
+ a
1121
+ end
1122
+
1123
+ def outObject(o,v,h)
1124
+ arr = o.to_yaml.split("\n")
1125
+ arr.each do |a|
1126
+ $outwin.setpos(v+=1,h)
1127
+ $outwin.addstr a.strip
1128
+ end
1129
+ end
1130
+
1131
+ def outMessage(m)
1132
+ $outwin.clear
1133
+ $outwin.setpos(1,0)
1134
+ $outwin.addstr m
1135
+ $outwin.refresh
1136
+ end
1137
+
1138
+
1139
+
1140
+
1141
+ ############# run!! ##########################################################
1142
+
1143
+
1144
+
1145
+ $credentials_hash = {}
1146
+ $configFile = File.expand_path("~/.csdk")
1147
+ $outwin = nil
1148
+ $api = nil
1149
+ $user = $layer = nil
1150
+ $error = nil
1151
+ $file_hash = {}
1152
+
1153
+ if File.exists? $configFile
1154
+ begin
1155
+ $credentials_hash = JSON.parse( File.read($configFile), symbolize_names: true)
1156
+ rescue
1157
+ end
1158
+ end
1159
+
1160
+
1161
+ $fileMenu = Menu.new( [
1162
+ MenuItem.new('2','layer: add or edit fields', lambda { layerAddFields }),
1163
+ MenuItem.new('3','layer: add or edit context', lambda { not_yet }),
1164
+ MenuItem.new('5','file: load & analyse', lambda{loadFile}),
1165
+ MenuItem.new('6','file: rename fields', lambda{editFields}),
1166
+ MenuItem.new('7','file: show sample', lambda{showSample}),
1167
+ MenuItem.new('8','file: assign geom columns', lambda { assignGeometry }),
1168
+ MenuItem.new('9','file: do import', lambda { doImport }),
1169
+ MenuItem.new('s','file: save processed file', lambda { saveFile }),
1170
+ MenuItem.new('c','file: save config', lambda { saveConfig }),
1171
+ MenuItem.new('q','back', lambda{$layer = nil; $topMenu.run})
1172
+ ])
1173
+
1174
+ $layerMenu = Menu.new( [
1175
+ MenuItem.new('1','list layers', lambda{showLayers}),
1176
+ MenuItem.new('2','layer summary', lambda { layerSummary }),
1177
+
1178
+ MenuItem.new('3','create layer', lambda{createLayer}),
1179
+ MenuItem.new('4','edit layer', lambda { editLayer }),
1180
+ MenuItem.new('5','clear objects', lambda { clearLayer }),
1181
+
1182
+ MenuItem.new('6','delete layer', lambda { deleteLayer }),
1183
+ MenuItem.new('q','back', lambda{$topMenu.run})
1184
+ ])
1185
+
1186
+ $userMenu = Menu.new( [
1187
+ MenuItem.new('1','list users', lambda{showUsers}),
1188
+ MenuItem.new('2','create user', lambda{createUser}),
1189
+ MenuItem.new('3','edit user', lambda{ editUser } ),
1190
+ MenuItem.new('4','delete user', lambda { deleteUser }),
1191
+ MenuItem.new('q','back', lambda{$topMenu.run})
1192
+ ])
1193
+
1194
+ $topMenu = Menu.new( [
1195
+ MenuItem.new('1','set host & credentials', lambda{setHost}),
1196
+ MenuItem.new('2','layers', lambda{$layerMenu.run}),
1197
+ MenuItem.new('3','edit user', lambda{}),
1198
+ MenuItem.new('4','analysis / upload', lambda{
1199
+ $fileMenu.run
1200
+ }),
1201
+ MenuItem.new('q','quit', lambda{doExit})
1202
+ ])
1203
+
1204
+
1205
+ while ARGV[0]
1206
+ s = ARGV.shift
1207
+ $nohttps = s=='nohttps'
1208
+ end
1209
+
1210
+ init_screen
1211
+ nl
1212
+ noecho
1213
+
1214
+ trap(:INT) do
1215
+ doExit
1216
+ end
1217
+
1218
+
1219
+ doConnect
1220
+ runFromTop