From 82da3d0602202d446104368cfd5e71b6dcb1b905 Mon Sep 17 00:00:00 2001 From: Brian Kelly Date: Mon, 16 Dec 2024 17:23:14 -0600 Subject: [PATCH] GeoJSON support with passing tests --- .../js/invenio_previewer/geojson.js | 43 ++++++++++++++ .../scss/invenio_previewer/geojson.scss | 14 +++++ .../js/invenio_previewer/geojson.js | 43 ++++++++++++++ .../scss/invenio_previewer/geojson.scss | 14 +++++ invenio_previewer/extensions/json_prismjs.py | 58 +++++++++++++++---- .../templates/invenio_previewer/geojson.html | 14 +++++ .../invenio_previewer/geojson.html | 14 +++++ invenio_previewer/webpack.py | 6 ++ tests/test_macros.py | 30 ++++++++++ 9 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 invenio_previewer/assets/bootstrap3/js/invenio_previewer/geojson.js create mode 100644 invenio_previewer/assets/bootstrap3/scss/invenio_previewer/geojson.scss create mode 100644 invenio_previewer/assets/semantic-ui/js/invenio_previewer/geojson.js create mode 100644 invenio_previewer/assets/semantic-ui/scss/invenio_previewer/geojson.scss create mode 100644 invenio_previewer/templates/invenio_previewer/geojson.html create mode 100644 invenio_previewer/templates/semantic-ui/invenio_previewer/geojson.html diff --git a/invenio_previewer/assets/bootstrap3/js/invenio_previewer/geojson.js b/invenio_previewer/assets/bootstrap3/js/invenio_previewer/geojson.js new file mode 100644 index 00000000..f726036b --- /dev/null +++ b/invenio_previewer/assets/bootstrap3/js/invenio_previewer/geojson.js @@ -0,0 +1,43 @@ +/* + * This file is part of Invenio. + * Copyright (C) 2015-2024 CERN. + * + * Invenio is free software; you can redistribute it and/or modify it + * under the terms of the MIT License; see LICENSE file for more details. + */ + +import L from "leaflet" +import "leaflet/dist/leaflet.css"; + +document.addEventListener("DOMContentLoaded", () => { + const mapElement = document.getElementById('map') + const fileUri = mapElement.getAttribute('data-file-uri') + const fileUrl = new URL(fileUri, window.location.href); + + const map = L.map('map').setView([0, 0], 13); + + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap' + }).addTo(map); + + fetch(fileUrl.href).then(res => res.json()).then(data => { + const geoJsonLayer = L.geoJson(data, { + style: { + color: "#3399ff", + weight: 2, + opacity: 0.5 + }, + pointToLayer: (feature, latlng) => L.circleMarker(latlng, { + radius: 4, + fillColor: "#3399ff", + color: "#3399ff", + weight: 2, + opacity: 1, + fillOpacity: 0.8 + }) + }) + geoJsonLayer.addTo(map); + map.fitBounds(geoJsonLayer.getBounds()); + }); +}); diff --git a/invenio_previewer/assets/bootstrap3/scss/invenio_previewer/geojson.scss b/invenio_previewer/assets/bootstrap3/scss/invenio_previewer/geojson.scss new file mode 100644 index 00000000..effc0730 --- /dev/null +++ b/invenio_previewer/assets/bootstrap3/scss/invenio_previewer/geojson.scss @@ -0,0 +1,14 @@ +/* + * This file is part of Invenio. + * Copyright (C) 2015-2018 CERN. + * + * Invenio is free software; you can redistribute it and/or modify it + * under the terms of the MIT License; see LICENSE file for more details. + */ + +@import "leaflet/dist/leaflet.css"; + +#map { + width: 100%; + height: 480px; +} diff --git a/invenio_previewer/assets/semantic-ui/js/invenio_previewer/geojson.js b/invenio_previewer/assets/semantic-ui/js/invenio_previewer/geojson.js new file mode 100644 index 00000000..f726036b --- /dev/null +++ b/invenio_previewer/assets/semantic-ui/js/invenio_previewer/geojson.js @@ -0,0 +1,43 @@ +/* + * This file is part of Invenio. + * Copyright (C) 2015-2024 CERN. + * + * Invenio is free software; you can redistribute it and/or modify it + * under the terms of the MIT License; see LICENSE file for more details. + */ + +import L from "leaflet" +import "leaflet/dist/leaflet.css"; + +document.addEventListener("DOMContentLoaded", () => { + const mapElement = document.getElementById('map') + const fileUri = mapElement.getAttribute('data-file-uri') + const fileUrl = new URL(fileUri, window.location.href); + + const map = L.map('map').setView([0, 0], 13); + + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: '© OpenStreetMap' + }).addTo(map); + + fetch(fileUrl.href).then(res => res.json()).then(data => { + const geoJsonLayer = L.geoJson(data, { + style: { + color: "#3399ff", + weight: 2, + opacity: 0.5 + }, + pointToLayer: (feature, latlng) => L.circleMarker(latlng, { + radius: 4, + fillColor: "#3399ff", + color: "#3399ff", + weight: 2, + opacity: 1, + fillOpacity: 0.8 + }) + }) + geoJsonLayer.addTo(map); + map.fitBounds(geoJsonLayer.getBounds()); + }); +}); diff --git a/invenio_previewer/assets/semantic-ui/scss/invenio_previewer/geojson.scss b/invenio_previewer/assets/semantic-ui/scss/invenio_previewer/geojson.scss new file mode 100644 index 00000000..8c416e2f --- /dev/null +++ b/invenio_previewer/assets/semantic-ui/scss/invenio_previewer/geojson.scss @@ -0,0 +1,14 @@ +/* + * This file is part of Invenio. + * Copyright (C) 2015-2020 CERN. + * + * Invenio is free software; you can redistribute it and/or modify it + * under the terms of the MIT License; see LICENSE file for more details. + */ + +@import "leaflet/dist/leaflet.css"; + +#map { + width: 100%; + height: 480px; +} diff --git a/invenio_previewer/extensions/json_prismjs.py b/invenio_previewer/extensions/json_prismjs.py index 433328b2..646bbb51 100644 --- a/invenio_previewer/extensions/json_prismjs.py +++ b/invenio_previewer/extensions/json_prismjs.py @@ -6,17 +6,18 @@ # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. -"""Previews a JSON file.""" +"""Previews a JSON or GeoJSON file.""" import json from collections import OrderedDict +from operator import truediv from flask import current_app, render_template from ..proxies import current_previewer from ..utils import detect_encoding -previewable_extensions = ["json"] +previewable_extensions = ["json", "geojson"] def render(file): @@ -46,15 +47,52 @@ def validate_json(file): def can_preview(file): """Determine if the given file can be previewed.""" - return file.is_local() and file.has_extensions(".json") and validate_json(file) + return ( + file.is_local() + and file.has_extensions(".json", ".geojson") + and validate_json(file) + ) + + +def is_geojson(file): + """Determine if the given file is a GeoJSON file.""" + if file.has_extensions(".geojson"): + return True + + with file.open() as fp: + encoding = detect_encoding(fp, default="utf-8") + file_content = fp.read().decode(encoding) + json_data = json.loads(file_content) + + valid_types = [ + "Feature", + "FeatureCollection", + "GeometryCollection", + "Point", + "MultiPoint", + "LineString", + "MultiLineString", + "Polygon", + "MultiPolygon", + ] + + return json_data.get("type", None) in valid_types def preview(file): """Render the appropriate template with embed flag.""" - return render_template( - "invenio_previewer/json_prismjs.html", - file=file, - content=render(file), - js_bundles=current_previewer.js_bundles + ["prism_js.js"], - css_bundles=["prism_css.css"], - ) + if is_geojson(file): + return render_template( + "invenio_previewer/geojson.html", + file=file, + js_bundles=current_previewer.js_bundles + ["geojson_js.js"], + css_bundles=["geojson_css.css"], + ) + else: + return render_template( + "invenio_previewer/json_prismjs.html", + file=file, + content=render(file), + js_bundles=current_previewer.js_bundles + ["prism_js.js"], + css_bundles=["prism_css.css"], + ) diff --git a/invenio_previewer/templates/invenio_previewer/geojson.html b/invenio_previewer/templates/invenio_previewer/geojson.html new file mode 100644 index 00000000..623a791d --- /dev/null +++ b/invenio_previewer/templates/invenio_previewer/geojson.html @@ -0,0 +1,14 @@ +{# -*- coding: utf-8 -*- + + This file is part of Invenio. + Copyright (C) 2016-2024 CERN. + + Invenio is free software; you can redistribute it and/or modify it + under the terms of the MIT License; see LICENSE file for more details. +#} + +{%- extends config.PREVIEWER_ABSTRACT_TEMPLATE %} + +{% block panel %} +
+{% endblock %} diff --git a/invenio_previewer/templates/semantic-ui/invenio_previewer/geojson.html b/invenio_previewer/templates/semantic-ui/invenio_previewer/geojson.html new file mode 100644 index 00000000..623a791d --- /dev/null +++ b/invenio_previewer/templates/semantic-ui/invenio_previewer/geojson.html @@ -0,0 +1,14 @@ +{# -*- coding: utf-8 -*- + + This file is part of Invenio. + Copyright (C) 2016-2024 CERN. + + Invenio is free software; you can redistribute it and/or modify it + under the terms of the MIT License; see LICENSE file for more details. +#} + +{%- extends config.PREVIEWER_ABSTRACT_TEMPLATE %} + +{% block panel %} +
+{% endblock %} diff --git a/invenio_previewer/webpack.py b/invenio_previewer/webpack.py index 2bbbd594..39048019 100644 --- a/invenio_previewer/webpack.py +++ b/invenio_previewer/webpack.py @@ -35,6 +35,8 @@ themes={ "bootstrap3": dict( entry={ + "geojson_js": "./js/invenio_previewer/geojson.js", + "geojson_css": "./scss/invenio_previewer/geojson.scss", "papaparse_csv": "./js/invenio_previewer/csv_previewer/init.js", "previewer_theme": "./js/invenio_previewer/previewer_theme.js", "prism_js": "./js/invenio_previewer/prismjs.js", @@ -43,6 +45,7 @@ }, dependencies={ "bootstrap-sass": "~3.3.5", + "leaflet": "^1.9.4", "papaparse": "^5.4.1", "flightjs": "~1.5.1", "font-awesome": "~4.5.0", @@ -71,6 +74,8 @@ ), "semantic-ui": dict( entry={ + "geojson_js": "./js/invenio_previewer/geojson.js", + "geojson_css": "./scss/invenio_previewer/geojson.scss", "papaparse_csv": "./js/invenio_previewer/csv_previewer/init.js", "previewer_theme": "./js/invenio_previewer/previewer_theme.js", "prism_js": "./js/invenio_previewer/prismjs.js", @@ -88,6 +93,7 @@ "flightjs": "~1.5.1", "font-awesome": "~4.5.0", "jquery": "^3.3.1", + "leaflet": "^1.9.4", "papaparse": "^5.4.1", "prismjs": "^1.15.0", "video.js": "^8.6.1", diff --git a/tests/test_macros.py b/tests/test_macros.py index 7c222a6c..a209c3f1 100644 --- a/tests/test_macros.py +++ b/tests/test_macros.py @@ -107,6 +107,36 @@ def test_zip_extension(testapp, webassets, record, zip_fp): assert "Zipfile is not previewable" in res.get_data(as_text=True) +def test_json_extension_valid_geojson_file(testapp, webassets, record): + """Test view with GeoJSON files.""" + json_data = ( + '{"type": "FeatureCollection","features":' + '[{"type":"Feature","geometry": {"type": "Point",' + '"coordinates": [102.0, 0.5]},"properties":{}}]}' + ) + create_file(record, "geo.geojson", BytesIO(b(json_data))) + + with testapp.test_client() as client: + res = client.get(preview_url(record["control_number"], "geo.geojson")) + + assert '