-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adds Geospatial custom fields and Map/Attribute viewer to Record view #221
Merged
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
493b553
Leaflet map with WMS visuals and clicking for feature attributes
spilth b70c2e0
Styling tweaks
spilth 7108560
Prevent Attributes table from stacking on narrow displays
spilth d430ff8
Map and Attributes table design tweaks
spilth f0b16e2
More map and attribute styling tweaks
spilth eca1901
Switch to booleans and config vars for GeoServer metadata and URLs
spilth 0f21920
Don't show map preview when files are restricted and user is anonymous
spilth 1eb9322
Only show map previws for Restricted files to logged in users
spilth c87dd63
Adds section display Web Services information
spilth a041aa7
Leverage more test fixtures
spilth 3a6d084
Fix tests after rebase
spilth fba234e
Update NYU copyright year in description.html
spilth 21e986b
Place map underneath invenio-menu
spilth 72e948f
Refactor JavaScript for clarity and simplifcation
spilth 3287977
Only attempt to load attributes if there's a WFS URL
spilth 8adcae3
Improve description of proxy endpoints
spilth File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ This file was automatically generated by 'invenio-cli init'. | |
For the full list of settings and their values, see | ||
https://inveniordm.docs.cern.ch/reference/configuration/. | ||
""" | ||
from invenio_records_resources.services.custom_fields import TextCF, BooleanCF | ||
#required to use custom permissions | ||
from ultraviolet_saml.handlers import acs_handler_factory | ||
from ultraviolet_permissions.policies import UltraVioletPermissionPolicy | ||
|
@@ -71,6 +72,13 @@ APP_DEFAULT_SECURE_HEADERS = { | |
"blob:", # for pdf preview | ||
# Add your own policies here (e.g. analytics) | ||
], | ||
'img-src': [ | ||
"'self'", | ||
'data:', | ||
"https://*.basemaps.cartocdn.com", | ||
"https://maps-public.geo.nyu.edu", | ||
"https://maps-restricted.geo.nyu.edu", | ||
] | ||
}, | ||
'content_security_policy_report_only': False, | ||
'content_security_policy_report_uri': None, | ||
|
@@ -421,6 +429,8 @@ if APP_ENVIRONMENT == 'local': | |
'[email protected]': 'adminUV' | ||
} | ||
|
||
RATELIMIT_ENABLED = False | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is only enabled in |
||
|
||
|
||
# UltraViolet custom vars | ||
# ======================= | ||
|
@@ -451,3 +461,72 @@ NYU_LIBRARIES_HOMEPAGE = 'https://library.nyu.edu' | |
|
||
# Max file size for displaying download buttons and link (50 GB) | ||
MAX_FILE_SIZE = 50 * 1024 * 1024 * 1024 | ||
|
||
RDM_NAMESPACES = { | ||
"geoserver": "https://geoserver.org/" | ||
} | ||
|
||
RDM_CUSTOM_FIELDS = [ | ||
BooleanCF(name="geoserver:has_wms_layer"), | ||
BooleanCF(name="geoserver:has_wfs_layer"), | ||
TextCF(name="geoserver:layer_name"), | ||
TextCF(name="geoserver:bounds"), | ||
] | ||
|
||
GEOSERVER_PUBLIC_URL = "https://maps-public.geo.nyu.edu/geoserver/sdr" | ||
GEOSERVER_RESTRICTED_URL = "https://maps-restricted.geo.nyu.edu/geoserver/sdr" | ||
|
||
RDM_CUSTOM_FIELDS_UI = [ | ||
{ | ||
"section": _("GeoServer"), | ||
"hide_from_landing_page": True, | ||
"fields": [ | ||
dict( | ||
field="geoserver:layer_name", | ||
ui_widget="Input", | ||
props=dict( | ||
label="Layer Name", | ||
placeholder="sdr:nyu_2451_12345", | ||
icon="pencil", | ||
description="Name of the GeoServer Layer this data can be found in", | ||
required=False | ||
) | ||
), | ||
dict( | ||
field="geoserver:has_wms_layer", | ||
ui_widget="BooleanCheckbox", | ||
props=dict( | ||
label="WMS Layer?", | ||
icon="check", | ||
description="Does this record have a WMS layer in GeoServer?", | ||
required=False, | ||
trueLabel="Yes", | ||
falseLabel="No", | ||
) | ||
), | ||
dict( | ||
field="geoserver:has_wfs_layer", | ||
ui_widget="BooleanCheckbox", | ||
props=dict( | ||
label="WFS Layer?", | ||
icon="check", | ||
description="Does this record have a WFS layer in GeoServer?", | ||
required=False, | ||
trueLabel="Yes", | ||
falseLabel="No", | ||
) | ||
), | ||
dict( | ||
field="geoserver:bounds", | ||
ui_widget="Input", | ||
props=dict( | ||
label="Bounds", | ||
placeholder="ENVELOPE(-178.2176, -66.969275, 71.406235818, 18.921781818)", | ||
icon="pencil", | ||
description="The envelope for the bounds of this layer", | ||
required=False | ||
) | ||
) | ||
] | ||
} | ||
] |
Empty file.
16 changes: 16 additions & 0 deletions
16
site/ultraviolet/assets/semantic-ui/css/ultraviolet/geoserver.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
@import "leaflet/dist/leaflet.css"; | ||
@import 'ol/ol.css'; | ||
|
||
#map { | ||
width: 100%; | ||
height: 440px; | ||
margin-top: 1em; | ||
cursor: crosshair !important; | ||
border: 1px solid #ced4da; | ||
z-index: 99; /* Place map under invenio-menu */ | ||
} | ||
|
||
.attributes { | ||
max-height: 450px; | ||
overflow-y: scroll; | ||
} |
162 changes: 162 additions & 0 deletions
162
site/ultraviolet/assets/semantic-ui/js/ultraviolet/geoserver.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
import L from "leaflet" | ||
import "leaflet/dist/leaflet.css"; | ||
|
||
const populateAttributeTable = (data) => { | ||
const attributesElement = document.getElementById("attributes"); | ||
attributesElement.innerHTML = ''; | ||
|
||
Object.keys(data.properties).sort().forEach(property => { | ||
const nameTd = document.createElement('td'); | ||
nameTd.textContent = property | ||
const typeTd = document.createElement("td") | ||
typeTd.textContent = data.properties[property] | ||
|
||
const tr = document.createElement("tr") | ||
tr.appendChild(nameTd) | ||
tr.appendChild(typeTd) | ||
attributesElement.appendChild(tr) | ||
}) | ||
} | ||
|
||
const retrieveAttributeTypes = (wfsUrl, layerNames) => { | ||
const attributesElement = document.getElementById("attributes"); | ||
|
||
const formData = new FormData(); | ||
formData.append("url", wfsUrl); | ||
formData.append("layers", layerNames); | ||
|
||
fetch("/geoserver/describe_feature_type", { | ||
method: "POST", body: formData | ||
}) | ||
.then(response => response.json()) | ||
.then(data => { | ||
const attributes = data.featureTypes[0].properties.sort((a, b) => { | ||
return a.name.localeCompare(b.name) | ||
}) | ||
|
||
attributes.forEach(attribute => { | ||
const nameTd = document.createElement('td'); | ||
nameTd.textContent = attribute.name | ||
const typeTd = document.createElement("td") | ||
typeTd.textContent = attribute.localType | ||
|
||
const tr = document.createElement("tr") | ||
tr.appendChild(nameTd) | ||
tr.appendChild(typeTd) | ||
attributesElement.appendChild(tr) | ||
}) | ||
}) | ||
.catch(error => { | ||
console.error('Error:', error); | ||
}); | ||
}; | ||
|
||
const addFeatureInspectionHandler = (map, url, layerNames) => { | ||
map.on("click", async (e) => { | ||
const attributesElement = document.getElementById("attributes"); | ||
attributesElement.innerHTML = '<tr><td colspan="2">Loading...</td>'; | ||
|
||
try { | ||
const response = await fetch("/geoserver/get_feature_info", { | ||
method: "POST", headers: { | ||
"Content-Type": "application/json", | ||
}, body: JSON.stringify({ | ||
url: url, | ||
layers: layerNames, | ||
bbox: map.getBounds().toBBoxString(), | ||
width: Math.round(document.getElementById("map").clientWidth), | ||
height: Math.round(document.getElementById("map").clientHeight), | ||
query_layers: layerNames, | ||
x: Math.round(e.containerPoint.x), | ||
y: Math.round(e.containerPoint.y), | ||
}), | ||
}); | ||
|
||
if (!response.ok) throw new Error("Network response was not ok."); | ||
|
||
const response_data = await response.json(); | ||
|
||
if (response_data.hasOwnProperty("error") || response_data.hasOwnProperty('exceptions') || response_data.features.length === 0) { | ||
const attributesElement = document.getElementById("attributes"); | ||
attributesElement.innerHTML = '<tr><td colspan="2">No feature found</td>'; | ||
|
||
return; | ||
} | ||
const data = response_data.features[0]; | ||
|
||
populateAttributeTable(data); | ||
} catch (error) { | ||
console.error("Fetch error: ", error); | ||
} | ||
}); | ||
} | ||
|
||
const zoomMapToBoundingBox = (map) => { | ||
const mapElement = document.getElementById("map"); | ||
const preview = mapElement.getAttribute("data-preview"); | ||
const bounds = mapElement.getAttribute("data-bounds") | ||
|
||
const regex = /ENVELOPE\(([-\d.]+), ([-\d.]+), ([-\d.]+), ([-\d.]+)\)/; | ||
const match = bounds.match(regex); | ||
|
||
if (match) { | ||
let [_, minLon, maxLon, minLat, maxLat] = match; | ||
|
||
const bounds = [[minLat, minLon], [maxLat, maxLon]]; | ||
|
||
map.fitBounds(bounds); | ||
|
||
if (preview === "False") { | ||
map.addLayer(L.rectangle(bounds, {color: "#3388FF", weight: 3})); | ||
} | ||
} | ||
}; | ||
|
||
const addWmsLayer = (map) => { | ||
const mapElement = document.getElementById("map"); | ||
const baseUrl = mapElement.getAttribute("data-wms-url") | ||
const layerName = mapElement.getAttribute("data-layer-name") | ||
|
||
const wmsLayer = L.tileLayer.wms(baseUrl, { | ||
layers: layerName, | ||
format: 'image/png', | ||
transparent: true, | ||
opacity: 0.75 | ||
}); | ||
|
||
wmsLayer.addTo(map); | ||
wmsLayer.setOpacity(0.75); | ||
}; | ||
|
||
const addWfsInspection = map => { | ||
const attributesElement = document.getElementById("attributes"); | ||
|
||
if (attributesElement) { | ||
const wfsUrl = attributesElement.getAttribute("data-wfs-url"); | ||
const layerNames = attributesElement.getAttribute("data-layer-names"); | ||
|
||
addFeatureInspectionHandler(map, wfsUrl, layerNames); | ||
retrieveAttributeTypes(wfsUrl, layerNames); | ||
} | ||
}; | ||
|
||
document.addEventListener("DOMContentLoaded", async () => { | ||
const mapElement = document.getElementById("map"); | ||
const preview = mapElement.getAttribute("data-preview"); | ||
|
||
const map = L.map('map').setView([0, 0], 13); | ||
|
||
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{retina}.png', { | ||
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="http://carto.com/attributions">Carto</a>', | ||
maxZoom: 18, | ||
worldCopyJump: true, | ||
retina: "@2x", | ||
}).addTo(map); | ||
|
||
if (preview === "True") { | ||
addWmsLayer(map); | ||
addWfsInspection(map); | ||
} | ||
|
||
zoomMapToBoundingBox(map); | ||
}); |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import urllib | ||
|
||
from flask import request, Response | ||
from flask.views import MethodView | ||
|
||
import requests | ||
|
||
|
||
class DescribeFeatureType(MethodView): | ||
"""Proxy for GeoServer DescribeFeatureType requests.""" | ||
|
||
def post(self): | ||
"""Pass DescribeFeatureType requests to GeoServer and hand back results.""" | ||
url = request.form.get('url', default=None) | ||
layers = request.form.get('layers', default=None) | ||
|
||
query_string = urllib.parse.urlencode({ | ||
"outputFormat": "application/json", | ||
"request": "DescribeFeatureType", | ||
"service": "WFS", | ||
"typeName": layers, | ||
"version": "1.1.0", | ||
}) | ||
|
||
response = requests.get("{0}?{1}".format(url, query_string)) | ||
|
||
return Response(response.text, mimetype='application/json') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import urllib | ||
|
||
from flask import request, Response | ||
from flask.views import MethodView | ||
|
||
import requests | ||
|
||
|
||
class GetFeatureInfo(MethodView): | ||
"""Proxy for GeoServer GetFeatureInfo requests.""" | ||
|
||
def post(self): | ||
"""Pass GetFeatureInfo requests to GeoServer and hand back results.""" | ||
data = request.get_json() | ||
|
||
url = data.get('url', None) | ||
query_string = urllib.parse.urlencode({ | ||
"service": "WMS", | ||
"version": "1.1.1", | ||
"request": "GetFeatureInfo", | ||
"layers": data.get('layers', None), | ||
"query_layers": data.get('layers', None), | ||
"bbox": data.get('bbox', None), | ||
"width": data.get('width', None), | ||
"height": data.get('height', None), | ||
"x": data.get('x', None), | ||
"y": data.get('y', None), | ||
"srs": "EPSG:4326", | ||
"info_format": "application/json", | ||
"styles": "" | ||
}) | ||
|
||
response = requests.get("{0}?{1}".format(url, query_string)) | ||
|
||
return Response(response.text, mimetype='application/json') |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are here to allow pulling content from external sources on the front-end.