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:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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 () {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user