-
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
Open
spilth
wants to merge
16
commits into
geospatial
Choose a base branch
from
leaflet-geoserver-map-alternate
base: geospatial
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 11 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.
15 changes: 15 additions & 0 deletions
15
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,15 @@ | ||
@import "leaflet/dist/leaflet.css"; | ||
@import 'ol/ol.css'; | ||
|
||
#map { | ||
width: 100%; | ||
height: 440px; | ||
margin-top: 1em; | ||
cursor: crosshair !important; | ||
border: 1px solid #ced4da; | ||
} | ||
|
||
.attributes { | ||
max-height: 450px; | ||
overflow-y: scroll; | ||
} |
149 changes: 149 additions & 0 deletions
149
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,149 @@ | ||
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 describeFeatureType = (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 addInspection = (map, url, layerId) => { | ||
map.on("click", async (e) => { | ||
const attributesElement = document.getElementById("attributes"); | ||
attributesElement.innerHTML = '<tr><td colspan="2">Loading...</td>'; | ||
|
||
const wmsoptions = { | ||
url: url, | ||
layers: layerId, | ||
bbox: map.getBounds().toBBoxString(), | ||
width: Math.round(document.getElementById("map").clientWidth), | ||
height: Math.round(document.getElementById("map").clientHeight), | ||
query_layers: layerId, | ||
x: Math.round(e.containerPoint.x), | ||
y: Math.round(e.containerPoint.y), | ||
}; | ||
|
||
try { | ||
const response = await fetch("/geoserver/get_feature_info", { | ||
method: "POST", headers: { | ||
"Content-Type": "application/json", | ||
}, body: JSON.stringify(wmsoptions), | ||
}); | ||
|
||
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); | ||
} | ||
}); | ||
} | ||
|
||
document.addEventListener("DOMContentLoaded", async () => { | ||
const mapElement = document.getElementById("map"); | ||
const preview = mapElement.getAttribute("data-preview"); | ||
const bounds = mapElement.getAttribute("data-bounds") | ||
|
||
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") { | ||
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 attributesElement = document.getElementById("attributes"); | ||
const wfsUrl = attributesElement.getAttribute("data-wfs-url"); | ||
const layerNames = attributesElement.getAttribute("data-layer-names"); | ||
|
||
addInspection(map, wfsUrl, layerNames); | ||
describeFeatureType(wfsUrl, layerNames, attributesElement); | ||
} | ||
|
||
const regex = /ENVELOPE\(([-\d.]+), ([-\d.]+), ([-\d.]+), ([-\d.]+)\)/; | ||
const match = bounds.match(regex); | ||
|
||
if (match) { | ||
const minLon = parseFloat(match[1]); | ||
const maxLon = parseFloat(match[2]); | ||
const minLat = parseFloat(match[3]); | ||
const maxLat = parseFloat(match[4]); | ||
|
||
const bounds = [[minLat, minLon], [maxLat, maxLon]]; | ||
|
||
map.fitBounds(bounds); | ||
|
||
if (preview === "False") { | ||
map.addLayer(L.rectangle(bounds, {color: "#3388FF", weight: 3})); | ||
} | ||
} | ||
}); |
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): | ||
"""GeoServer view.""" | ||
|
||
def post(self): | ||
"""Return JSON""" | ||
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): | ||
"""GeoServer view.""" | ||
|
||
def post(self): | ||
"""Return JSON""" | ||
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') |
55 changes: 55 additions & 0 deletions
55
site/ultraviolet/templates/semantic-ui/ultraviolet/records/details/map.html
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,55 @@ | ||
{% set previewable = record.access.files == 'public' or (current_user and current_user.is_authenticated) %} | ||
{% set geoserver_base_url = config.GEOSERVER_PUBLIC_URL if (record.access.files == 'public') else config.GEOSERVER_RESTRICTED_URL %} | ||
|
||
{% if record.custom_fields["geoserver:layer_name"] and record.custom_fields["geoserver:has_wms_layer"] %} | ||
<h2>Map</h2> | ||
|
||
<div id="map" | ||
data-preview="{{ previewable }}" | ||
data-bounds="{{ record.custom_fields["geoserver:bounds"] }}" | ||
{% if previewable %} | ||
data-wms-url="{{ geoserver_base_url }}/wms" | ||
data-layer-name="{{ record.custom_fields["geoserver:layer_name"] }}" | ||
{% endif %} | ||
></div> | ||
|
||
{% if record.custom_fields["geoserver:has_wfs_layer"] and (previewable) %} | ||
<h3>Attributes</h3> | ||
|
||
<div class="attributes"> | ||
<table class="ui unstackable very compact table striped selectable"> | ||
<thead> | ||
<tr> | ||
<th>Attribute</th> | ||
<th>Type/Value</th> | ||
</tr> | ||
</thead> | ||
<tbody id="attributes" | ||
data-wfs-url="{{ geoserver_base_url }}/wfs" | ||
data-layer-names="{{ record.custom_fields["geoserver:layer_name"] }}"></tbody> | ||
</table> | ||
</div> | ||
{% endif %} | ||
|
||
{% if previewable %} | ||
<h3>Web Services</h3> | ||
|
||
<dl class="details-list"> | ||
<dt class="ui tiny header">Layer Name</dt> | ||
<dd><code>{{ record.custom_fields["geoserver:layer_name"] }}</code></dd> | ||
{% if record.custom_fields["geoserver:has_wfs_layer"] %} | ||
<dt class="ui tiny header">Web Feature Service (WFS)</dt> | ||
<dd><code>{{ geoserver_base_url }}/wfs</code></dd> | ||
{% endif %} | ||
{% if record.custom_fields["geoserver:has_wms_layer"] %} | ||
<dt class="ui tiny header">Web Mapping Service (WMS)</dt> | ||
<dd><code>{{ geoserver_base_url }}/wms</code></dd> | ||
{% endif %} | ||
</dl> | ||
{% endif %} | ||
{% endif %} | ||
|
||
{% block javascript %} | ||
{{ webpack['geoserver_js.js'] }} | ||
{{ webpack['geoserver_css.css'] }} | ||
{% endblock %} |
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.