add support for geojson in the map component

this adds support for new geometries, including polygons

Closes https://github.com/lovasoa/SQLpage/issues/119
This commit is contained in:
lovasoa
2023-11-07 23:52:38 +01:00
parent 2369a4b52c
commit 17681fb672
11 changed files with 86 additions and 40 deletions
+1
View File
@@ -12,6 +12,7 @@
- Fix a bug where [cookie](https://sql.ophir.dev/documentation.sql?component=cookie#component) removal set the cookie value to the empty string instead of removing the cookie completely.
- Support form submission using the [button](https://sql.ophir.dev/documentation.sql?component=button#component) component using its new `form` property. This allows you to create a form with multiple submit buttons that submit the form to different targets.
- Custom icons and colors for markers in the [map](https://sql.ophir.dev/documentation.sql?component=map#component) component.
- Add support for GeoJSON in the [map](https://sql.ophir.dev/documentation.sql?component=map#component) component. This makes it much more generic and allows you to display any kind of geographic data, including areas, on a map very easily. This plays nicely with PostGIS and Spatialite which can return GeoJSON directly from SQL queries.
## 0.15.0 (2023-10-29)
- New function: [`sqlpage.path`](https://sql.ophir.dev/functions.sql?function=path#function) to get the path of the current page.
+5
View File
@@ -128,6 +128,11 @@ To run on a server, you can use [the docker image](https://hub.docker.com/r/lova
- Optionally, you can also mount a directory containing sqlpage's configuration file,
custom components, and migrations
(see [configuration.md](./configuration.md)) to `/etc/sqlpage` in the container.
- If you want to build your own docker image, taking the raw sqlpage image as a base is not recommended, since it is extremely stripped down and probably won't contain the dependencies you need. Instead, you can take debian as a base and simply copy the sqlpage binary from the official image to your own image:
- ```Dockerfile
FROM debian:stable-slim
COPY --from=lovasoa/sqlpage:main /usr/local/bin/sqlpage /usr/local/bin/sqlpage
```
### On Mac OS, with homebrew
@@ -1,14 +0,0 @@
FROM debian:12-slim
WORKDIR /var/www
ADD https://github.com/lovasoa/SQLpage/releases/download/v0.7.2/sqlpage-linux.tgz .
RUN apt-get update && \
apt-get -y install sqlite3 libsqlite3-mod-spatialite && \
tar xvzf sqlpage-linux.tgz && \
rm sqlpage-linux.tgz
COPY . .
CMD ["sqlite3"]
@@ -1,14 +1,10 @@
FROM debian:12-slim
FROM debian:stable-slim
WORKDIR /var/www
ADD https://github.com/lovasoa/SQLpage/releases/download/v0.10.1/sqlpage-linux.tgz .
COPY --from=lovasoa/sqlpage:main /usr/local/bin/sqlpage /usr/local/bin/sqlpage
RUN apt-get update && \
apt-get -y install sqlite3 libsqlite3-mod-spatialite && \
tar xvzf sqlpage-linux.tgz && \
rm sqlpage-linux.tgz
apt-get -y install libsqlite3-mod-spatialite
COPY . .
CMD ["sqlite3"]
CMD ["sqlpage"]
@@ -16,7 +16,9 @@ SELECT 'map' as component,
SELECT title,
ST_Y(geom) AS latitude,
ST_X(geom) AS longitude,
'point.sql?id=' || id as link
'point.sql?id=' || id as link,
'red' as color,
'world-pin' as icon
FROM spatial_data;
@@ -109,6 +109,7 @@ and SQLPage adds a few more:
- `icon_img`: generate an svg icon from a *tabler* icon name
- `markdown`: renders markdown text
- `each_row`: iterates over the rows of a query result
- `typeof`: returns the type of a value (`string`, `number`, `boolean`, `object`, `array`, `null`)
## Overwriting the default components
+4 -1
View File
@@ -114,6 +114,8 @@ select
WHEN 'true' THEN 'TRUE'
WHEN 'false' THEN 'FALSE'
WHEN 'null' THEN 'NULL'
WHEN 'object' THEN 'JSON(' || quote(t.value) || ')'
WHEN 'array' THEN 'JSON(' || quote(t.value) || ')'
ELSE quote(t.value)
END as val,
CASE parent.fullkey
@@ -121,7 +123,8 @@ select
ELSE parent.key
END as key
from t inner join t parent on parent.id = t.parent
where t.atom is not null
where ((parent.fullkey = '$' and t.type != 'array')
or (parent.type = 'array' and parent.path = '$'))
),
key_val_padding as (select
CASE WHEN key LIKE '% %' THEN format('"%s"', replace(key, '"', '""')) ELSE key END as key,
@@ -41,7 +41,7 @@ VALUES (
(
'map',
'latitude',
'Latitude of the marker',
'Latitude of the marker. Required if geojson is not set.',
'REAL',
FALSE,
FALSE
@@ -49,7 +49,7 @@ VALUES (
(
'map',
'longitude',
'Longitude of the marker',
'Longitude of the marker. Required if geojson is not set.',
'REAL',
FALSE,
FALSE
@@ -57,7 +57,7 @@ VALUES (
(
'map',
'title',
'Title of the marker',
'Title of the marker, displayed on hover and in the tooltip when the marker is clicked.',
'TEXT',
FALSE,
TRUE
@@ -65,7 +65,7 @@ VALUES (
(
'map',
'link',
'A link to associate to the marker''s title',
'A link to associate to the marker''s title. If set, the marker tooltip''s title will be clickable and will open the link.',
'TEXT',
FALSE,
TRUE
@@ -73,7 +73,7 @@ VALUES (
(
'map',
'description',
'Plain text description of the marker, as plain text',
'Plain text description of the marker, to be displayed in a tooltip when the marker is clicked.',
'TEXT',
FALSE,
TRUE
@@ -81,7 +81,7 @@ VALUES (
(
'map',
'description_md',
'Description of the marker, in markdown',
'Description of the marker, in markdown, rendered in a tooltip when the marker is clicked.',
'TEXT',
FALSE,
TRUE
@@ -101,6 +101,14 @@ VALUES (
'COLOR',
FALSE,
TRUE
),
(
'map',
'geojson',
'A GeoJSON geometry (line, a polygon, ...) to display on the map. Can be styled using geojson properties using the name of leaflet path options.',
'JSON',
FALSE,
TRUE
)
;
-- Insert an example usage of the map component into the example table
@@ -114,12 +122,17 @@ VALUES (
),
(
'map',
'Map of Paris',
'Map of Paris.
Illustrates the use custom styling, and [GeoJSON](https://en.wikipedia.org/wiki/GeoJSON) to display a line between two points.
In a real-world scenario, the GeoJSON could be generated by calling PostGIS''s
[`ST_AsGeoJSON`](https://postgis.net/docs/ST_AsGeoJSON.html) or
Spatialite''s [`AsGeoJSON`](https://www.gaia-gis.it/gaia-sins/spatialite-sql-5.1.0.html#p3misc) functions on a geometry column.',
JSON(
'[
{ "component": "map", "title": "Paris", "zoom": 11, "latitude": 48.85, "longitude": 2.34, "height": 400 },
{ "title": "Notre Dame", "icon": "building-castle", "color": "indigo", "latitude": 48.8530, "longitude": 2.3498, "description_md": "A beautiful cathedral.", "link": "https://en.wikipedia.org/wiki/Notre-Dame_de_Paris" },
{ "title": "Eiffel Tower", "icon": "tower", "color": "yellow", "latitude": 48.8584, "longitude": 2.2945, "description_md": "A tall tower. [Wikipedia](https://en.wikipedia.org/wiki/Eiffel_Tower)" }
]'
{ "title": "Eiffel Tower", "icon": "tower", "color": "yellow", "latitude": 48.8584, "longitude": 2.2945, "description_md": "A tall tower. [Wikipedia](https://en.wikipedia.org/wiki/Eiffel_Tower)" },
{ "title": "Tower to Cathedral", "geojson": {"type": "LineString", "coordinates": [[2.2945, 48.8584], [2.3498, 48.8530]]}, "color": "teal", "description": "A nice 45 minutes walk." }
]'
)
);
+27 -6
View File
@@ -54,23 +54,44 @@ function sqlpage_map() {
}
}
function addMarker(marker_elem, map) {
const coords = marker_elem.dataset.coords.split(",").map(c => parseFloat(c));
const { dataset } = marker_elem;
const options = {
color: marker_elem.dataset.color,
title: marker_elem.getElementsByTagName("h3")[0].textContent.trim(),
};
const color = marker_elem.dataset.color;
const marker =
dataset.coords ? createMarker(marker_elem, options)
: createGeoJSONMarker(marker_elem, options);
marker.addTo(map).bindPopup(marker_elem);
}
function createMarker(marker_elem, options) {
const coords = marker_elem.dataset.coords.split(",").map(c => parseFloat(c));
const icon_obj = marker_elem.getElementsByClassName("mapicon")[0];
if (icon_obj) {
options.icon = L.divIcon({
html: icon_obj,
className: `border-0 bg-${color || 'primary'} bg-gradient text-white rounded-circle p-2 shadow`,
className: `border-0 bg-${options.color || 'primary'} bg-gradient text-white rounded-circle p-2 shadow`,
iconSize: [42, 42],
iconAnchor: [21, 5],
iconAnchor: [21, 21],
});
}
const marker = L.marker(coords, options).addTo(map);
marker.bindPopup(marker_elem);
return L.marker(coords, options);
}
function createGeoJSONMarker(marker_elem, options) {
let geojson = JSON.parse(marker_elem.dataset.geojson);
if (options.color) {
options.color = get_tabler_color(options.color) || options.color;
}
function style({ properties }) {
if (typeof properties !== "object") return options;
return {...options, ...properties};
}
return L.geoJSON(geojson, { style });
}
}
function get_tabler_color(name) {
return getComputedStyle(document.documentElement).getPropertyValue('--tblr-' + name);
}
document.addEventListener('DOMContentLoaded', function () {
+9 -1
View File
@@ -17,7 +17,15 @@
</div>
<div class="d-none" hidden>
{{~#each_row~}}
<div class="marker" data-coords="{{latitude}},{{longitude}}" {{#if color}}data-color="{{color}}"{{/if}}>
<div class="marker"
{{#if latitude}}data-coords="{{latitude}},{{longitude}}"{{/if}}
{{#if color}}data-color="{{color}}"{{/if}}
{{#if geojson}}data-geojson="
{{~#if eq (typeof geojson) 'string'}}{{geojson}}
{{~else~}}{{stringify geojson}}
{{~/if~}}
"{{/if}}
>
<h3>
{{~#if icon~}}
<span class="mapicon">{{~icon_img icon~}}</span>
+10
View File
@@ -297,6 +297,16 @@ impl AllTemplates {
);
handlebars.register_helper("buildinfo", Box::new(buildinfo_helper));
handlebars_helper!(typeof_helper: |x: Json| match x {
JsonValue::Null => "null",
JsonValue::Bool(_) => "boolean",
JsonValue::Number(_) => "number",
JsonValue::String(_) => "string",
JsonValue::Array(_) => "array",
JsonValue::Object(_) => "object",
});
handlebars.register_helper("typeof", Box::new(typeof_helper));
let mut this = Self {
handlebars,
split_templates: FileCache::new(),