This repository has been archived by the owner on Oct 6, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
readme.txt
465 lines (336 loc) · 22.6 KB
/
readme.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
Setup on Heroku
---------------------------------------------
Setup Postgres extension:
run heroku pg:psql and "create extension postgis;" in there
Clone the local database with:
PGUSER=alex PGPASSWORD=alex heroku pg:push safetyfirst_development HEROKU_POSTGRESQL_TEAL --app safetyfirstnode-staging
Set search path in heroku pg:psql:
- Run `heroku pg:credentials DATABASE_URL -a csl-safesitenode` to get the name of the database
- Run this in psql:
alter database <DB name, e.g. df9bvjqtn3i2oh> set search_path = '$user', 'public', 'postgis';
- Restart the psql session, and the search path can be checked with:
show search_path;
Setup DATABASE_URL env variable for Heroku app, this is required for node-db-migrate to connect to the database:
heroku config:set DATABASE_URL=`heroku config:get HEROKU_POSTGRESQL_TEAL_URL`
Migrations can be run pretty much normally:
heroku run node db-migrate -e prod -v up
Fork the database
------------------------------------------------------------------------------------------------------------------------
After forking an application/database, it's necessary to update the search path (see setup instructions).
Restore locally from a database dump
------------------------------------------------------------------------------------------------------------------------
createdb safesite_dev
psql -d safesite_dev # open the DB in psql
In psql:
create schema postgis;
alter database safesite_dev set search_path = '$user', 'postgis', 'public';
\q
Back on the command line:
cd /usr/local/Cellar/postgis/2.1.4_1/share/postgis
psql -d safesite_dev -f postgis.sql
psql -d safesite_dev -f postgis_comments.sql
psql -d safesite_dev -f spatial_ref_sys.sql
In psql:
alter database safesite_dev set search_path = '$user', 'public', 'postgis';
cd <dump location>
pg_restore --verbose --no-acl --no-owner -d safesite_dev 2015-08-30.dump
Create a project
------------------------------------------------------------------------------------------------------------------------
$ tools/create_project staging "Blake''s Crossing" 1 Australia/Adelaide polygon ../kml/Blake\'s\ crossing/boundary.json
Debug SQL queries
------------------------------------------------------------------------------------------------------------------------
When a query has CTEs (intermediate sub-selects before the main select) or can be refactored into such form, then
it's possible to see the output of intermediate steps:
- Create the debug table:
create table debug_table (id serial, t text, r text);
- Add a new CTE statement into the query such as:
debug_a as (insert into debug_table(t,r) select 'vis_int', row(visit_intervals.*)::text from visit_intervals),
This will dump all the data from the specified sub-table into debug_table.
Note that modifying CTEs (like the debug one) have to be at the top level, which means this only works with the
`query` method of the DB client (not with `jsonQuery` or `jsonArrayQuery`).
Importing LendLease KMLs into the DB as GeoJSON features
------------------------------------------------------------------------------------------------------------------------
Run these commands:
iconv -f UTF-16 -t UTF-8 source_cutfill.kml > cutfill.kml
togeojson cutfill.kml > cutfill.json
Then in psql:
\set content `cat cutfill.json`
select j->>'type' as type, j->'properties'->>'Name' as name, j->'geometry' as g
from (select json_array_elements(:'content'::json->'features') as j) as sub;
insert into geometries(overlay_id, properties, geometry)
select ?? as overlay_id, ('{"name":"' || (j->'properties'->>'name') || '"}')::json as properties,
ST_SetSRID(ST_MakePolygon(ST_Force2D(ST_GeomFromGeoJSON(j->>'geometry'))), 4326) as geometry
from (select json_array_elements(:'content'::json->'features') as j) as sub
where j->'properties'->>'name' ilike '%cut%';
Downloading LendLease drone imagery
------------------------------------------------------------------------------------------------------------------------
Download with 10 parallel connections and resume:
aria2c --file-allocation=none -c -x 10 -s 10 -d . <url>
caffeinate -w <aria pid>
Visualising positions from Heroku
------------------------------------------------------------------------------------------------------------------------
psql <conn string from heroku pg:credentials DATABASE_URL> -t -c
"select st_astext(st_point(lon, lat)) from positions where user_id = 361 and created_at > '2013-05-12 20:00:00' order by created_at desc" | geojsonify | geojsonio
Simplify KML geometries
------------------------------------------------------------------------------------------------------------------------
ogr2ogr -simplify 0.000001 -f KML simplified.kml original.kml
Useful SQL queries
------------------------------------------------------------------------------------------------------------------------
== Set download boundaries
------------------------------------------------------------
update projects set download_boundary = (select st_simplify(st_buffer(geometry::geography, 1000)::geometry, 0.005)
from geometries join overlays on overlay_id = overlays.id
where (overlays.properties->>'is_boundary')::bool and project_id = projects.id)
, updated_at = clock_timestamp() + '1 minute'::interval
where id = 29;
== Extract positions as (almost correct) GeoJSON:
------------------------------------------------------------
- Execute this in RubyMine:
with features as
(with coords as (select id, 'Point' as type, ARRAY[lon, lat] as coordinates from positions where user_id = 74),
props as (select id, accuracy, speed, created_at, recorded_at from positions where user_id = 74)
select 'Feature' as type, row_to_json(coords.*) as geometry, row_to_json(props.*) as properties
from coords
join props on coords.id = props.id)
select '"FeatureCollection"' as type, json_agg(row_to_json(features.*)) as features from features
- Copy to clipboard using the JSON extractor
- the data will be correct but wrongly wrapped in square brackets
== Create a project manually using a JSON boundary in a file:
------------------------------------------------------------
-- boundary.json contains a FeatureCollection (e.g. as output by togeojson when converting from KML)
\set bound `cat ../kml/Dunedin/boundary.json`
begin;
with var as (select 'Dunedin' as project_name, 'TT5AA5' as project_cid, 99 as tpl_boundary_id,
ST_SetSRID(ST_MakePolygon(ST_Force2D(ST_GeomFromGeoJSON(:'bound'::json#>>'{features,0,geometry}'))), 4326) as geometry
)
, project as (
insert into projects (name, cid, download_boundary)
select var.project_name, var.project_cid,
-- set download boundary to be ~1km away from the project boundary
st_simplify(st_buffer(var.geometry::geography, 1000)::geometry, 0.005)
from var
returning id, download_boundary
)
, boundary as (
insert into overlays (name, project_id, display_order, properties)
select 'Boundary', project.id, 100,
(select properties from overlays where id = var.tpl_boundary_id)
from var cross join project
returning id
)
, geom as (
insert into geometries (overlay_id, properties, geometry)
select boundary.id, '{"name": "Project boundary"}'::json, var.geometry
from boundary cross join var
)
select * from geom;
== Insert a geometry using a KML fragment:
------------------------------------------------------------
begin; insert into geometries (overlay_id, properties, geometry) values (98,
'{"name": "Clearing line"}'::json, ST_SetSRID(ST_Force2D(ST_GeomFromKML('<Polygon><outerBoundaryIs><LinearRing><coordinates>151.2010508495358,-33.89454214039346,0 151.2010244030993,-33.89550086321904,0 151.2019655764939,-33.89559574476716,0 151.2029649224187,-33.89571585462653,0 151.2033830872738,-33.89562535128356,0 151.2035801360156,-33.89469774959951,0 151.2026081804326,-33.8947415646257,0 151.2026543751532,-33.89431695006977,0 151.2018223789218,-33.89423260397754,0 151.2013299266485,-33.89431101594254,0 151.2010508495358,-33.89454214039346,0</coordinates></LinearRing></outerBoundaryIs></Polygon>')), 4326));
== Insert a marker:
------------------------------------------------------------
insert into geometries (geometry, properties, overlay_id)
values (ST_SetSRID(ST_GeomFromGeoJSON('{"type":"Point","coordinates":[174.859505,-41.129325153]}'), 4326), to_json('{"name": "JHA Station 1", "icon": "info"}'), 3);
== Get restricted_areas entries in a readable form:
------------------------------------------------------------
select properties, ST_AsGeoJSON(geometry) from geometries where overlay_id = 3;
== Format UTC time for the client:
------------------------------------------------------------
to_char(site_overlay_files.updated_at, 'YYYY-MM-DD HH24:MI:SSZ') as updated_at
== Get a convex hull:
------------------------------------------------------------
insert into geometries (overlay_id, properties, geometry)
values (57, '{}'::json,
(select ST_Buffer(ST_ConcaveHull(geometry, 0.99), 0.001) from geometries where overlay_id = 7));
== Check for duplicate event records
------------------------------------------------------------
with pairs as (
select id, user_id, geometry_id, created_at, type,
lead(type, 1) over (partition by user_id, project_id, date_trunc('day', created_at at time zone 'Australia/NSW'), geometry_id
order by created_at rows between current row and 1 following) as pair_type
from events
order by user_id
)
select id, user_id, geometry_id, created_at from pairs where type = pair_type order by user_id;
== Get project activity stats
------------------------------------------------------------
select name, count(distinct user_id) as users, count(events)/2 as area_visits,
last(events.created_at order by events.created_at) at time zone 'Australia/NSW' as latest_activity
from events join projects on project_id = projects.id group by name;
== Check for event positions outside download boundaries
------------------------------------------------------------
select * from events where position is not null and
(select count(*) from projects
where ST_Contains(download_boundary, ST_SetSRID(ST_Point((events.position->>'lon')::double precision,
(events.position->>'lat')::double precision), 4326))) = 0
order by user_id, created_at;
== Get platform & device information
------------------------------------------------------------
select user_id, last(properties->>'platform' order by created_at) as platform, last(properties->>'device' order by created_at) as device,
last(properties->>'api_version' order by created_at) as api
from info_events where type = 'app_start' group by user_id order by platform, device;
== Get paving scan summary
------------------------------------------------------------
select properties->>'docket_id', string_agg(properties->>'step', ',' order by created_at)
from events
where type = 'concrete_movement' and project_id = 17 and created_at > now() - '5 hours'::interval
group by properties->>'docket_id' order by properties->>'docket_id' desc;
== Export query results to CSV
------------------------------------------------------------
\copy (select * from positions where user_id = 974 order by created_at) to '974.csv' with csv header
== Set a JSON property
------------------------------------------------------------
update users_hq set permissions = json_object_set_key(permissions, 'drawing', true) where id = 1;
== Detect possible stuck positions
------------------------------------------------------------
This is a query for Android users, and count > 100 filter assumes high frequency positions:
with u as (select distinct user_id as id from info_events
where user_id in (select distinct user_id from events where project_id = 17)
and type = 'app_start' and created_at > now() - interval '60 days'
and properties->>'platform' ilike 'android%')
select count(*) as cnt, user_id, date_trunc('hour', created_at) as hour, lon, lat, accuracy
, altitude, altitude_accuracy, speed, heading
from positions
where user_id in (select id from u) and created_at > now() - interval '60 days'
group by user_id, hour, lon, lat, accuracy, altitude, altitude_accuracy, speed, heading
having count(*) > 100
order by hour desc;
False positives are possible so it's still necessary to inspect the positions manually.
== Get user stats for the last 30 days
------------------------------------------------------------
with last_synced_at as (
select user_id, max(synced_at) as at from users_projects group by user_id
)
, records as (
select user_id, last(properties->>'platform' order by created_at) as platform, last(properties->>'device' order by created_at) as device,
last(properties->>'api_version' order by created_at) as api
from info_events
where type = 'app_start' group by user_id
)
, grouped_records as (
select records.user_id, platform, device, api, at
from records
left join last_synced_at on records.user_id = last_synced_at.user_id
where at >= now() - '30 days'::interval
)
select 'Active mobile users today' as name, count(*)::text as value
from grouped_records
where at at time zone 'Australia/NSW' >= date_trunc('day', now() at time zone 'Australia/NSW')
union
select 'Active mobile users (30 days)', count(*)::text from grouped_records
union
select 'API versions in use', string_agg(api::text, ', ' order by api) from (select distinct api from grouped_records) a
union
select 'iOS versions', string_agg(platform, ', ' order by platform) from (select distinct platform from grouped_records where platform ilike 'ios%') b
union
select 'Android versions', string_agg(platform, ', ' order by platform) from (select distinct platform from grouped_records where platform ilike 'android%') c
union
select 'Mobile errors today', count(*)::text from info_events where type = 'error' and date_trunc('day', now() at time zone 'Australia/NSW') = date_trunc('day', created_at at time zone 'Australia/NSW')
union
-- Times have to be "at time zone 'Australia/NSW'" once sessions table is fixed
select 'Active HQ users today', count(*)::text from sessions where date_trunc('day', now()) = date_trunc('day', updated_at)
union
-- Times have to be "at time zone 'Australia/NSW'" once sessions table is fixed
select 'Active HQ users (30 days)', count(distinct data->>'userId')::text from sessions where updated_at >= now() - '30 days'::interval
order by name
== Get speed stats
------------------------------------------------------------
with t1 as (select created_at at time zone 'NZ' as local,
ST_Distance(ST_SetSRID(ST_Point(lon, lat), 4326)::geography,
ST_SetSRID(ST_Point(lag(lon) over(order by created_at), lag(lat) over(order by created_at)), 4326)::geography) as dist,
ST_Distance(ST_SetSRID(ST_Point(lon, lat), 4326)::geography,
ST_SetSRID(ST_Point(first(lon) over(partition by extract(day from created_at at time zone 'NZ') order by created_at), first(lat) over(partition by extract(day from created_at at time zone 'NZ') order by created_at)), 4326)::geography) as dist2,
speed, accel, heading, lon, lat, accuracy, created_at
from positions
where user_id = 995 and extract(hour from created_at at time zone 'NZ') between 5 and 8 and created_at > now() - interval '7 hours'
)
select local, dist, dist2, speed, accel, extract(epoch from created_at - lag(created_at) over (partition by extract(day from created_at at time zone 'NZ') order by created_at)) as tm, dist / extract(epoch from created_at - lag(created_at) over (partition by extract(day from created_at at time zone 'NZ') order by created_at)) as calc_speed, heading, lon, lat, accuracy, dist - accuracy as diff
from t1
order by created_at desc
== Get acceleration stats
------------------------------------------------------------
with accel as (select abs((accel->>'x')::double precision) as x, abs((accel->>'y')::double precision) as y, abs((accel->>'z')::double precision) as z, speed,
lag(speed, 1) over (order by created_at) as prev_speed1,
lag(speed, 2) over (order by created_at) as prev_speed2,
lag(speed, 3) over (order by created_at) as prev_speed3,
lead(speed, 1) over (order by created_at) as next_speed1,
lead(speed, 1) over (order by created_at) as next_speed2,
created_at
from positions where user_id = 1002 and created_at > '2016-05-26 20:03:00' and created_at < '2016-05-27 03:26:05')
, accel2 as (
select *, sqrt(x*x+y*y+z*z) as mag, greatest(x, y, z) as axmax
from accel
--where speed < 0.01 --and prev_speed = 0 and next_speed > 0
)
, accel3 as (
select *, ntile(10) over (order by mag) as ptile, (mag + lag(mag, 1) over (order by created_at) + lag(mag, 2) over (order by created_at)) / 3 as avg_mag, greatest(axmax + lag(axmax, 1) over (order by created_at) + lag(axmax, 2) over (order by created_at)) as axmax_3
from accel2
)
, accel4 as (
select *, ntile(10) over (order by axmax_3) as ptile2 from accel3
where
-- Stop error
--speed > 0.2 and ((next_speed2 < 0.01 and next_speed1 > 0.01 and prev_speed1 < 0.01) or (next_speed1 < 0.01 and prev_speed1 > 0.01 and prev_speed2 < 0.01))
-- Move start
speed > 0.01 and prev_speed1 > 0.01 and prev_speed2 > 0.01 and prev_speed3 < 0.01
)
-- select min(mag), max(mag), avg(mag), min(ptile), max(ptile)
-- from accel4
-- select * from accel4
select ptile2, min(axmax_3), max(axmax_3), avg(axmax_3) from accel4 group by ptile2 order by ptile2
API v4 time sending format
server: `extract(epoch from <timestamp column>)::int as time` --> number of seconds
client: `moment.tz(time * 1000, timeZone)` --> moment at project's time zone
API v4 timestamps scheme
------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------
File | Function/param | P/C | Producer/Consumer | Zone | Format
---------|----------------------------------|-----|---------------------------|---------|----------
weather | created_at | <-- | Meteo data | UTC | timestamp
| created_at | --> | node user.getWeather | |
---------|----------------------------------|-----|---------------------------|---------|----------
user | recordInfoEvent, | <-- | mobile.Uplink | mobile | moment json
| recordPosition, | | | |
| recordEvent, | | | |
| recordObservation, | | | |
| recordVehicle, created_at | | | |
| recordVehicle, rego_Exp_date | <-- | mobile, prestartChecklist | mobile | moment
| getUpdatedPosition | --> | mobile | project | 'DD/MM/YY HH24:MI:SS'
| getWeather | --> | mobile, weather icon | UTC | timestamp
| getUpdatedCollections, | | | |
| created_at | --> | mobile, collections | project | 'DD/MM/YY HH24:MI:SS'
| getUpdatedVehicle, rego_exp_date | --> | mobile, vehiclesCollection| mobile | timestamp
---------|----------------------------------|-----|---------------------------|---------|-----------
reports | getProjectVisits timestamp | --> | | | seconds
| date | --> | | | 'DD/MM/YY HH24:MI:SS'
| arrived_at | --> | | | 'HH24:MI'
| departed_at | --> | | | 'HH24:MI'
| getArea date | --> | | none | 'DD/MM/YY HH24:MI:SS'
| duration | --> | | project | seconds
| getSpeedBands duration | --> | | project | seconds
| getTimeline timestamp | --> | | | seconds
| created_at | --> | | | 'DD/MM/YY HH24:MI:SS'
| getConcreteTests as_load_time | --> | | project | 'DD/MM/YY HH24:MI:SS'
| as_test_time | --> | | project | 'DD/MM/YY HH24:MI:SS'
| as_dump_time | --> | | project | 'DD/MM/YY HH24:MI:SS'
| getBreaks day | --> | | project | 'DD/MM/YY HH24:MI:SS'
| day_start_utc | --> | reports, getBreaks | utc | seconds
| day_end_utc | --> | reports, getBreaks | utc | seconds
| start | --> | | project | 'DD/MM/YY HH24:MI:SS'
| end | --> | | project | 'DD/MM/YY HH24:MI:SS'
| coord | --> | | | msc
---------|----------------------------------|-----|---------------------------|---------|------------
project | getBaseLayers updated_at | --> | BaseLayer.addDroneImage | project | 'DD/MM/YY HH24:MI:SS'
| getUsers last_position_at| --> | UserWatcher | project | 'DD/MM/YY HH24:MI:SS'
| getPositions last_position_at| --> | UserWatcher | project | 'DD/MM/YY HH24:MI:SS'
| created_at | --> | | project | 'DD/MM/YY HH24:MI:SS'
| getPositionTimeline timestamp | --> | | | msc
| next_timestamp | --> | | | msc
| getTimelineInfo start | --> | | | msc
| finish | --> | | | msc
---------|----------------------------------|-----|---------------------------|---------|------------
paving | getBatchPlantInfo | --> | | project | 'HH24:MI'
| getSlump hour | --> | | project | "hour"
| getProduction hour | --> | | project | "hour"
|