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.
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