Skip to content

Commit

Permalink
Merge branch 'master' into update-in-background-job
Browse files Browse the repository at this point in the history
  • Loading branch information
ebrelsford committed Dec 19, 2024
2 parents b8f703a + d59f44f commit caeb1cc
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 65 deletions.
61 changes: 42 additions & 19 deletions api-scripts/sync-datasets.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python
import urllib.request
import json
import os
import requests
Expand Down Expand Up @@ -180,31 +181,48 @@ def get_raster_layers_metadata(raster_resources):


def get_vector_layer_metadata(vector_resource):
vector_type = None
feature_count = None
bounds = [-180, -90, 180, 90]

# Confirm data exists at url, get some information about it
url = vector_resource['url']
try:
with urllib.request.urlopen(url) as req:
data = json.load(req)

# Does this GeoJSON exist?
head_request = requests.head(url)
if head_request.status_code != 200:
print('Failed to access', url)
print('Status code:', head_request.status_code)
features = data['features']
feature_count = len(features)
vector_type = features[0]['geometry']['type']
except Exception as e:
print('Failed to access vector dataset', url)
return None

# If it exists, get all the info about it
# Get bounds from metadata
metadata_url = vector_resource['metadata_url']
try:
# TODO get bounds
bounds = [-180, -90, 180, 90]

return {
'name': vector_resource['name'],
'type': 'vector',
'url': url,
'bounds': bounds,
}
with urllib.request.urlopen(metadata_url) as req:
yaml_data = yaml.safe_load(req.read())
bounding_box = yaml_data['spatial']['bounding_box']
bounds = [
bounding_box['xmin'],
bounding_box['ymin'],
bounding_box['xmax'],
bounding_box['ymax'],
]
except Exception as e:
print('Failed to access', url)
print('Status code:', head_request.status_code)
print('Failed to access vector metadata', metadata_url)
return None

return {
'name': vector_resource['name'],
'type': 'vector',
'url': url,
'bounds': bounds,
'vector_type': vector_type,
'feature_count': feature_count,
}


def get_vector_layers_metadata(vector_resources):
return filter(None, [get_vector_layer_metadata(r) for r in vector_resources])
Expand All @@ -223,15 +241,20 @@ def get_mappreview_metadata(dataset, zip_sources):
for shp_source in shp_sources:
path = shp_source.replace('\\', '/')
path_start = path.split('/')[0]
path_end = '/'.join(path.split('/')[1:]).replace('.shp', '.geojson')
path_end = '/'.join(path.split('/')[1:])

base = '/'.join(zip_resource['url'].split('/')[0:-1])
base = base.replace('https://storage.cloud.google.com/', 'https://storage.googleapis.com/')
url = f'{base}/{path_start}/geojsons/{path_end}'
geojson_path_end = path_end.replace('.shp', '.geojson')
url = f'{base}/{path_start}/geojsons/{geojson_path_end}'
name = path.split('/')[-1]

metadata_path_end = path_end.replace('.shp', '.shp.yml')
metadata_url = f'{base}/{path_start}/{metadata_path_end}'

vector_resources.append({
'name': name,
'metadata_url': metadata_url,
'url': url,
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,77 @@
.mappreview .mappreview-mapboxgl-popup .mapboxgl-popup-content {
max-height: 200px;
overflow-y: auto;
border-radius: 6px;
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.10);
}

.mappreview .mapboxgl-popup-close-button {
font-size: 2em;
color: var(--color-dark-gray);
top: 0.25rem;
right: 0.25rem;
}

.mappreview .mappreview-mapboxgl-popup h3 {
font-size: 1.25em;
color: var(--color-purple);
color: var(--color-dark-plum);
margin-right: 2rem;
}

.mappreview .popup-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 1.5rem;
.mappreview .popup-grid {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 0.25rem;
}

.mappreview .popup-key {
font-weight: bold;
}

.mappreview .mapboxgl-legend-list {
font-family: var(--bs-font-sans-serif);
border-radius: 6px;
padding: 0.5rem;
padding-right: 2rem;
}

.mappreview .mapboxgl-ctrl-group .mapboxgl-legend-list button {
font-size: 0;
color: transparent;
}

.mappreview .mapboxgl-legend-close-button:after {
content: '×';
color: var(--color-dark-gray);
font-size: 1.25rem;
line-height: 1.25rem;
}

.mappreview .mapboxgl-legend-list br {
display: none;
}

.mappreview .mapboxgl-legend-list label {
font-weight: 600;
}

.mappreview .mapboxgl-legend-list label:after {
content: '';
}

.mappreview .mapboxgl-legend-list input[type="checkbox"] {
vertical-align: middle;
}

.mappreview .mapboxgl-legend-title-label {
display: none;
}

.mappreview .mapboxgl-legend-list {
overflow-y: auto;
}

.mappreview .mapboxgl-legend-onlyRendered-checkbox,
.mappreview .mapboxgl-legend-onlyRendered-label {
display: none;
}
142 changes: 102 additions & 40 deletions src/ckanext-mappreview/ckanext/mappreview/assets/js/mappreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ ckan.module("mappreview", function ($, _) {
debug: false,
},

vectorColors: [
'#E04F39',
'#F9A44A',
'#FEC51D',
'#A57DAE',
],

/**
* Linear scale pixel values to [0, 255] range
*/
Expand Down Expand Up @@ -79,20 +86,61 @@ ckan.module("mappreview", function ($, _) {
};
},

_getVectorLayer: function (layer) {
return {
id: layer.name,
type: 'fill',
source: layer.name,
};
_getVectorLayers: function (layer, index) {
const color = this.vectorColors[index % this.vectorColors.length];

if (layer.vector_type === 'Point') {
return {
id: layer.name,
type: 'circle',
source: layer.name,
paint: {
'circle-radius': 5,
'circle-color': color,
}
};
}
else if (layer.vector_type === 'Line') {
return {
id: layer.name,
type: 'line',
source: layer.name,
paint: {
'line-width': 5,
'line-color': color,
}
};
}
else if (layer.vector_type === 'Polygon') {
return [
{
id: `${layer.name}-outline`,
type: 'line',
source: layer.name,
paint: {
'line-width': 2,
'line-color': color,
}
},
{
id: layer.name,
type: 'fill',
source: layer.name,
paint: {
'fill-color': color,
'fill-opacity': 0.5
}
}
];
}
},

initialize: function () {
jQuery.proxyAll(this, '_getGlobalConfig');
jQuery.proxyAll(this, '_getRasterLayer');
jQuery.proxyAll(this, '_getRasterTilejsonUrl');
jQuery.proxyAll(this, '_getRasterPoint');
jQuery.proxyAll(this, '_getVectorLayer');
jQuery.proxyAll(this, '_getVectorLayers');

const config = JSON.parse(this.options.config.replace(/'/g, '"'));
const globalConfig = this._getGlobalConfig();
Expand Down Expand Up @@ -129,22 +177,31 @@ ckan.module("mappreview", function ($, _) {
}
});

const layers = config.layers.map(l => {
if (l.type === 'raster') {
return this._getRasterLayer(l);
}
else if (l.type === 'vector') {
return this._getVectorLayer(l);
}
else {
console.warn(`Unsupported layer type: ${l.type}`);
return null;
}
});
const layers = config.layers
.map((l, i) => {
if (l.type === 'raster') {
return this._getRasterLayer(l);
}
else if (l.type === 'vector') {
return this._getVectorLayers(l, i);
}
else {
console.warn(`Unsupported layer type: ${l.type}`);
return null;
}
})
.filter(l => l !== null)
.flat()
.toSorted((a, b) => {
const order = ['raster', 'fill', 'line', 'circle'];
return order.indexOf(a.type) - order.indexOf(b.type);
});

map.on('load', () => {
sources.forEach((source) => {
map.addSource(source.id, source);
// Avoid warning about id
const cleanSource = Object.fromEntries(Object.entries(source).filter(([k, v]) => k !== 'id'));
map.addSource(source.id, cleanSource);
});

layers.forEach((layer) => {
Expand All @@ -154,45 +211,50 @@ ckan.module("mappreview", function ($, _) {
const targets = Object.fromEntries(config.layers.map(l => [l.name, l.name]));

map.addControl(new MapboxLegendControl(targets, {
showDefault: false,
showDefault: true,
showCheckbox: true,
onlyRendered: false,
reverseOrder: true
}), 'top-right');
});

map.on('click', sources.filter(s => s.type === 'vector').map(s => s.id), (e) => {
let content = '';
map.on('click', async (e) => {
let popupContent;

e.features.forEach(f => {
content += `<h3>${f.layer.id}</h3>
<ul>
${Object.keys(f.properties).map(k => `<li>${k}: ${f.properties[k]}</li>`).join('')}
</ul>`;
});
const vectorLayers = layers.filter(s => s.type !== 'raster');
if (vectorLayers.length > 0) {
const vectorFeature = map.queryRenderedFeatures(e.point, { layers: vectorLayers.map(l => l.id) })[0];

const popup = new mapboxgl.Popup({ className: 'mappreview-mapboxgl-popup' })
.setLngLat(e.lngLat)
.setMaxWidth("300px")
.setHTML(content)
.addTo(map);
});
if (vectorFeature) {
const rows = Object.entries(vectorFeature.properties)
.map(([key, value]) => `
<div class="popup-key">${key}</div>
<div class="popup-value">${value}</div>
`);

map.on('click', async (e) => {
if (config.layers.length === 1 && config.layers[0].type === 'raster') {
popupContent = `<h3>${config.layers[0].name}</h3>
<div class="popup-grid">
${rows.join('')}
</div>`;
}
}

if (!popupContent && (config.layers.length === 1 && config.layers[0].type === 'raster')) {
const point = await this._getRasterPoint(config.layers[0], e.lngLat);
if (!point) return;

const content = `<h3>${config.layers[0].name}</h3>
<div class="popup-row">
popupContent = `<h3>${config.layers[0].name}</h3>
<div class="popup-grid">
<div class="popup-key">value</div>
<div class="popup-value">${point.values[0]}</div>
</div>`;
}

if (popupContent) {
const popup = new mapboxgl.Popup({ className: 'mappreview-mapboxgl-popup' })
.setLngLat(e.lngLat)
.setMaxWidth("300px")
.setHTML(content)
.setHTML(popupContent)
.addTo(map);
}
});
Expand Down

0 comments on commit caeb1cc

Please sign in to comment.