|
| 1 | +--- |
| 2 | +title: Generating GeoJSON in SQLite |
| 3 | +date: 2024-09-08 12:20:25 |
| 4 | +tags: |
| 5 | +- sqlite |
| 6 | +- geojson |
| 7 | +--- |
| 8 | + |
| 9 | +[GeoJSON](https://geojson.org/) is an open standard based on JSON to represent geographic features. It supports points, lines, polygons, and multipart collections of the previous feature types. This post demonstrates ways of leveraging point location data in a SQLite database as latitude and longitude and converting into GeoJSON for usage as a map layer. Given a SQLite **locations** table with the following schema: |
| 10 | + |
| 11 | +``` |
| 12 | +uuid TEXT |
| 13 | +timestamp TEXT |
| 14 | +json TEXT |
| 15 | +data BLOB |
| 16 | +``` |
| 17 | +and the `data` column has the point location data with accuracy values in json format: |
| 18 | + |
| 19 | +```json |
| 20 | +{ |
| 21 | + "location": |
| 22 | + { |
| 23 | + "lat": 13.5494602, |
| 24 | + "lon": 104.2505927 |
| 25 | + }, |
| 26 | + "recorded_at": "2024-09-08T01:33:36.625Z", |
| 27 | + "additional": |
| 28 | + { |
| 29 | + "accuracy": 6.78, |
| 30 | + }, |
| 31 | +} |
| 32 | +``` |
| 33 | + |
| 34 | +First let's filter out any points with accuracy levels above a certain threshold, e.g. 25M, by using JSON parsing: |
| 35 | + |
| 36 | +```sql |
| 37 | +SELECT * |
| 38 | +FROM locations |
| 39 | +WHERE json_extract(json(data), '$.additional.accuracy') > 25; |
| 40 | +``` |
| 41 | + |
| 42 | +The `json_extract(json(data), '$.additional.accuracy')` extracts the `accuracy` value from the `additional` object in the JSON data. |
| 43 | + |
| 44 | +Now we want to format the existing JSON data in the `data` column as GeoJSON. We can do this with the following query: |
| 45 | + |
| 46 | +```sql |
| 47 | +SELECT |
| 48 | + id, |
| 49 | + uuid, |
| 50 | + timestamp, |
| 51 | + json_object( |
| 52 | + 'type', 'Feature', |
| 53 | + 'geometry', json_object( |
| 54 | + 'type', 'Point', |
| 55 | + 'coordinates', json_array( |
| 56 | + json_extract(json(data), '$.location.lon'), |
| 57 | + json_extract(json(data), '$.location.lat') |
| 58 | + ) |
| 59 | + ), |
| 60 | + 'properties', json_object( |
| 61 | + 'id', id, |
| 62 | + 'uuid', uuid, |
| 63 | + 'timestamp', timestamp |
| 64 | + ) |
| 65 | + ) AS geojson |
| 66 | +FROM locations; |
| 67 | +``` |
| 68 | +This creates a GeoJSON feature object for each row which includes a point geometry and properties from the other columns. This results GeoJSON for each row in the table as individual points: |
| 69 | + |
| 70 | +```json |
| 71 | +{ |
| 72 | + "type": "Feature", |
| 73 | + "geometry": |
| 74 | + { |
| 75 | + "type": "Point", |
| 76 | + "coordinates": |
| 77 | + [ |
| 78 | + 104.2505927, |
| 79 | + 13.5494602 |
| 80 | + ] |
| 81 | + }, |
| 82 | + "properties": |
| 83 | + { |
| 84 | + "id": 547, |
| 85 | + "uuid": "af801da8-5f2e-42d6-b059-b5f7ce96f30c", |
| 86 | + "timestamp": "2024-09-08T01:33:36.625Z" |
| 87 | + } |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +In order to create single GeoJSON file representing all of the rows in the table we need to use a Common Table Expression (CTE) with a subquery that creates individual GeoJSON feature objects for each point and wrap this in a GeoJSON `FeatureCollection`: |
| 92 | + |
| 93 | +```sql |
| 94 | +WITH point_features AS ( |
| 95 | + SELECT |
| 96 | + json_object( |
| 97 | + 'type', 'Feature', |
| 98 | + 'geometry', json_object( |
| 99 | + 'type', 'Point', |
| 100 | + 'coordinates', json_array( |
| 101 | + json_extract(json(data), '$.location.lon'), |
| 102 | + json_extract(json(data), '$.location.lat') |
| 103 | + ) |
| 104 | + ), |
| 105 | + 'properties', json_object( |
| 106 | + 'id', id, |
| 107 | + 'uuid', uuid, |
| 108 | + 'timestamp', timestamp |
| 109 | + ) |
| 110 | + ) AS feature |
| 111 | + FROM locations |
| 112 | +) |
| 113 | +SELECT json_object( |
| 114 | + 'type', 'FeatureCollection', |
| 115 | + 'features', json_group_array(feature) |
| 116 | +) AS geojson_collection |
| 117 | +FROM point_features; |
| 118 | +``` |
| 119 | + |
| 120 | +This results in a single row with one column named `geojson_collection` containing a complete GeoJSON `FeatureCollection` object with all rows in the table: |
| 121 | + |
| 122 | +```json |
| 123 | +{ |
| 124 | + "type": "FeatureCollection", |
| 125 | + "features": |
| 126 | + [ |
| 127 | + { |
| 128 | + "type": "Feature", |
| 129 | + "geometry": |
| 130 | + { |
| 131 | + "type": "Point", |
| 132 | + "coordinates": |
| 133 | + [ |
| 134 | + 104.2505927, |
| 135 | + 13.5494602 |
| 136 | + ] |
| 137 | + }, |
| 138 | + "properties": |
| 139 | + { |
| 140 | + "id": 547, |
| 141 | + "uuid": "af801da8-5f2e-42d6-b059-b5f7ce96f30c", |
| 142 | + "timestamp": "2024-09-08T01:33:36.625Z" |
| 143 | + } |
| 144 | + }, |
| 145 | + ] |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +Copy the results into Sublime Text and view in a sample web view with [MapPreview](https://gh.jdoneill.com/2020/08/15/mappreview/) |
0 commit comments