diff options
Diffstat (limited to 'bin/gis')
| -rwxr-xr-x | bin/gis/match-road.sh | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/bin/gis/match-road.sh b/bin/gis/match-road.sh new file mode 100755 index 0000000..d9beaae --- /dev/null +++ b/bin/gis/match-road.sh | |||
| @@ -0,0 +1,148 @@ | |||
| 1 | #!/usr/bin/env bash | ||
| 2 | # | ||
| 3 | # Author: Pham | ||
| 4 | # | ||
| 5 | # This script accepts a single GPX file as parameter and | ||
| 6 | # output the processed GPX body to STDOUT, using Mapbox Map Matching API v4. | ||
| 7 | # read doc at: https://docs.mapbox.com/api/legacy/map-matching-v4/ | ||
| 8 | # | ||
| 9 | # Example: | ||
| 10 | # | ||
| 11 | # match-road.sh raw.gpx > new.gpx | ||
| 12 | # | ||
| 13 | # Hint: | ||
| 14 | # | ||
| 15 | # Remember to put Mapbox Access Token at the top! | ||
| 16 | |||
| 17 | #set -x | ||
| 18 | set -e | ||
| 19 | |||
| 20 | # put yout Mapbox token here | ||
| 21 | ACCESS_TOKEN=$(cat ~/settings/tokens/mapbox) | ||
| 22 | # number of coordinates for each Mapbox Map Matching API request, Maximum value is 100 | ||
| 23 | LIMIT=100 | ||
| 24 | # define the lowest confidence of accepted matched points | ||
| 25 | THRESHOLD=0.0001 | ||
| 26 | |||
| 27 | if [[ -z $1 ]]; then echo "You need to give a gpx file!"; exit 1; fi | ||
| 28 | ORIGIN_DATA=/tmp/$(basename $1).origin | ||
| 29 | RESPONSES=/tmp/$(basename $1).responses && true > $RESPONSES | ||
| 30 | MATCHED=/tmp/$(basename $1).matched | ||
| 31 | |||
| 32 | # extract data from the given gpx file | ||
| 33 | # only keep first point and remove the rest which in the same "second" | ||
| 34 | # input: [gpx format] | ||
| 35 | # output: [121.0179739,14.5515336] 1984-01-01T08:00:46 | ||
| 36 | get_data() { | ||
| 37 | sed -nr '/<trkpt /, /<\/trkpt>/ {H; /<\/trkpt>/ {x; s/\n/ /g; p; s/.*//; x}}' $1 | | ||
| 38 | sed -nr 'h; s/.*lon="([^"]+).*/\1/; H; g | ||
| 39 | s/.*lat="([^"]+).*/\1/; H; g | ||
| 40 | # If trkpt has no time, leave it blank | ||
| 41 | /time/ { | ||
| 42 | s/.*<time>([^.]+).*<\/time>.*/\1/ | ||
| 43 | H; g | ||
| 44 | } | ||
| 45 | s/^[^\n]+\n//; s/\n/ /g; p' | | ||
| 46 | sed -r 's/^([^ ]+) ([^ ]+)/[\1,\2]/' | | ||
| 47 | awk '!_[$2]++' | ||
| 48 | } | ||
| 49 | |||
| 50 | # Output GeoJSON object for Map Matching API | ||
| 51 | # input: [121.0179739,14.5515336] 1984-01-01T08:00:46 | ||
| 52 | # output: {type: "Feature", properties: {coordTimes: [...]}, geometry: {type: "LineString", coordinates: [...]}} | ||
| 53 | make_geojson() { | ||
| 54 | # change input to format like: [[lon, lat], time] | ||
| 55 | awk '{printf("[%s,\"%s\"]\n", $1, $2)}' | | ||
| 56 | jq '[inputs] | {type: "Feature", geometry: {type: "LineString", coordinates: map(.[0])}, properties: {coordTimes: (map(.[1]))}}' | ||
| 57 | } | ||
| 58 | |||
| 59 | # Read GeoJSON body from STDIN, and return result from Mapbox Map Matching API | ||
| 60 | query_matched_points() { | ||
| 61 | curl -X POST -s --data @- \ | ||
| 62 | --header "Content-Type:application/json" \ | ||
| 63 | https://api.mapbox.com/matching/v4/mapbox.driving.json?access_token=$ACCESS_TOKEN | ||
| 64 | } | ||
| 65 | |||
| 66 | # Get valid data from Map Matching API response | ||
| 67 | # output format is [coordinates] [index-of-original-data], like: | ||
| 68 | # [121.0179739,14.5515336] 35 | ||
| 69 | # If the point is newly added, the index would be -1, like | ||
| 70 | # [121.0189339,14.5525931] -1 | ||
| 71 | validate_matched_points() { | ||
| 72 | VALID_DATA=$(jq ".features[] | if(.properties.confidence < $THRESHOLD) then .geometry.coordinates=(.properties.indices|map(.+1)) else . end") | ||
| 73 | |||
| 74 | echo $VALID_DATA | | ||
| 75 | jq -cr '.properties | [.matchedPoints, (.indices | map(.+1))] | transpose[] | "\(.[0]) \(.[1])"' > $MATCHED | ||
| 76 | |||
| 77 | echo $VALID_DATA | jq -c '.geometry.coordinates[]' | | ||
| 78 | while read point; do | ||
| 79 | if [[ ${point:0:1} != '[' ]]; then | ||
| 80 | echo $(sed -n "$point p" $ORIGIN_DATA) | while read coor time; do echo $coor $(date -d $time +%s); done | ||
| 81 | sed -i 1d $MATCHED | ||
| 82 | elif head -1 $MATCHED | grep -F $point > /dev/null; then | ||
| 83 | index=$(head -1 $MATCHED | cut -d' ' -f2) | ||
| 84 | echo $point $(sed -n "$index p" $ORIGIN_DATA | cut -d' ' -f2 | date -f - +%s) | ||
| 85 | sed -i 1d $MATCHED | ||
| 86 | else | ||
| 87 | echo $point -1 | ||
| 88 | fi | ||
| 89 | done | ||
| 90 | } | ||
| 91 | |||
| 92 | # Put existing timestamps to matched points, and interpolate new timestamps into new points | ||
| 93 | complete_data() { | ||
| 94 | # interpolate timestamps to newly added points | ||
| 95 | awk '{COOR[NR][0]=$1; N++; COOR[NR][1]=$2} END{for (i=1; i<=N; i++) {printf COOR[i][0]; if (COOR[i][1] != -1) {print " "COOR[i][1]; LAST=i} else {while(COOR[i+n][1] == -1) n++; print " "COOR[LAST][1]+(COOR[i+n][1]-COOR[LAST][1])*(i-LAST)/(i+n-LAST)}}}' | | ||
| 96 | while read coor unix_time; do | ||
| 97 | # Transform unix timestamp into human readable time format, like following: | ||
| 98 | # Transform [121.018088,14.5516] 18.50 | ||
| 99 | # Into [121.018088,14.5516] 1970-01-01T08:00:18.50Z | ||
| 100 | echo $coor $(date -d @$unix_time +'%Y-%m-%dT%H:%M:%S.%2NZ') | ||
| 101 | done | ||
| 102 | } | ||
| 103 | |||
| 104 | # Make GPX format for output | ||
| 105 | # Take input with format: [lon,lat] [time] | ||
| 106 | make_gpx() { | ||
| 107 | sed -E 's/\[([^,]+),([^,]+)\] (.*)/ <trkpt lon="\1" lat="\2"><time>\3<\/time><\/trkpt>/' | | ||
| 108 | sed "1i \ | ||
| 109 | <gpx version=\"1.1\" creator=\"Garmin Connect\"\n\ | ||
| 110 | xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd\"\n\ | ||
| 111 | xmlns=\"http://www.topografix.com/GPX/1/1\"\n\ | ||
| 112 | xmlns:gpxtpx=\"http://www.garmin.com/xmlschemas/TrackPointExtension/v1\"\n\ | ||
| 113 | xmlns:gpxx=\"http://www.garmin.com/xmlschemas/GpxExtensions/v3\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n\ | ||
| 114 | <trk>\n <trkseg>" | | ||
| 115 | sed "\$a \ \ \ \ <\/trkseg>\n <\/trk>\n<\/gpx>" | ||
| 116 | } | ||
| 117 | |||
| 118 | get_data $1 > $ORIGIN_DATA | ||
| 119 | |||
| 120 | #RAW_REQUEST=$(basename $1 | tr '.' '_')_request.geojson | ||
| 121 | #cat $ORIGIN_DATA | make_geojson | jq '.properties.stroke="#ff0000"' > $RAW_REQUEST | ||
| 122 | |||
| 123 | # Consume raw data with serveral request | ||
| 124 | while [ -s $ORIGIN_DATA ]; do | ||
| 125 | # Take original data by limited points for each time: [121.0179739,14.5515336] 1984-01-01T08:00:46 | ||
| 126 | # Make geojson body: {type: "Feature", properties: {coordTimes: [...]}, geometry: {type: "LineString", coordinates: [...]}} | ||
| 127 | # Call Mapbox Map Matching API: {geometry: {coordinates: [...]}, properties: {confidence: 0.9, matching: [...], indices: [...]}} | ||
| 128 | # Get valid point and index by confidence value: [121.0179739,14.5515336] 5 | ||
| 129 | # complete_data | ||
| 130 | head -$LIMIT $ORIGIN_DATA | | ||
| 131 | make_geojson | | ||
| 132 | query_matched_points | | ||
| 133 | tee -a $RESPONSES | | ||
| 134 | validate_matched_points | | ||
| 135 | complete_data | ||
| 136 | |||
| 137 | # Remove processed raw data | ||
| 138 | sed -i "1,$LIMIT d" $ORIGIN_DATA | ||
| 139 | done | make_gpx | ||
| 140 | #make_geojson > test.geojson | ||
| 141 | |||
| 142 | #RAW_RESPONSE=$(basename $1 | tr '.' '_')_response.geojson | ||
| 143 | #MATCHED_POINTS=$(basename $1 | tr '.' '_')_matched.geojson | ||
| 144 | #jq . $RESPONSES | jq -s '.[0].features=[.[]|.features[]] | .[0] | del(.code) | .features=(.features|map(.properties.stroke="#00ff00"))' > $RAW_RESPONSE | ||
| 145 | #jq ".features=(.features|map(select(.properties.confidence>=$THRESHOLD).geometry.coordinates=.properties.matchedPoints)|map(.properties.stroke=\"#0000ff\"))" $RAW_RESPONSE > $MATCHED_POINTS | ||
| 146 | # | ||
| 147 | #DEBUG=$(basename $1 | tr '.' '_')_total.geojson | ||
| 148 | #cat $RAW_REQUEST $RAW_RESPONSE $MATCHED_POINTS | jq -s '{type: "FeatureCollection", features: ([.[0]] + .[1].features + .[2].features)}' > $DEBUG | ||