Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/source/publishing/ogcapi-maps.rst
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ required. An optional style name can be defined via `options.style`.
- `4326`
- `3857`

If `crs` is not provided, the server will default `4326`. If `crs-bbox` and `bbox` are not provided,
the server will default to the crs and bounding box defined in the configuration of the spatial extent.

Data visualization examples
---------------------------

Expand Down
23 changes: 16 additions & 7 deletions pygeoapi/api/maps.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,19 @@ def get_collection_map(api: API, request: APIRequest,

query_args['format_'] = request.params.get('f', 'png')
query_args['style'] = style
query_args['crs'] = CRS_CODES[request.params.get(
'crs', collection_def.get('crs', DEFAULT_CRS))]
query_args['bbox_crs'] = CRS_CODES[request.params.get(
'bbox-crs', collection_def.get('crs', DEFAULT_CRS))]
query_args['transparent'] = request.params.get('transparent', True)

query_args['crs'] = CRS_CODES.get(request.params.get(
'crs', DEFAULT_CRS))
query_args['bbox_crs'] = CRS_CODES.get(request.params.get(
'bbox-crs',
api.config['resources'][dataset]['extents']['spatial'].get(
'crs', DEFAULT_CRS))
)

if query_args['crs'] is None:
query_args['crs'] = DEFAULT_CRS
if query_args['bbox_crs'] is None:
query_args['bbox_crs'] = DEFAULT_CRS

try:
query_args['width'] = int(request.params.get('width', 500))
Expand All @@ -135,7 +143,8 @@ def get_collection_map(api: API, request: APIRequest,

LOGGER.debug('Processing bbox parameter')
try:
bbox = request.params.get('bbox').split(',')
bbox = request.params.get(
'bbox').split(',')
if len(bbox) != 4:
exception = {
'code': 'InvalidParameterValue',
Expand All @@ -145,6 +154,7 @@ def get_collection_map(api: API, request: APIRequest,
LOGGER.error(exception)
return headers, HTTPStatus.BAD_REQUEST, to_json(
exception, api.pretty_print)

except AttributeError:
bbox = api.config['resources'][dataset]['extents']['spatial']['bbox'] # noqa
try:
Expand All @@ -163,7 +173,6 @@ def get_collection_map(api: API, request: APIRequest,
if query_args['bbox_crs'] != query_args['crs']:
LOGGER.debug(f'Reprojecting bbox CRS: {query_args["crs"]}')
bbox = transform_bbox(bbox, query_args['bbox_crs'], query_args['crs'])

query_args['bbox'] = bbox

LOGGER.debug('Processing datetime parameter')
Expand Down
13 changes: 12 additions & 1 deletion pygeoapi/crs.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,19 @@ def transform_bbox(bbox: list, from_crs: Union[str, pyproj.CRS],

from_crs_obj = get_crs(from_crs)
to_crs_obj = get_crs(to_crs)

transform_func = pyproj.Transformer.from_crs(
from_crs_obj, to_crs_obj).transform
from_crs_obj, to_crs_obj, always_xy=True).transform

# Clip values to max and min lat of WebMercator,
# to avoid infinte pole distortion
if to_crs_obj.to_epsg() == 3857:
bbox = [
bbox[0],
max(-85.0511, bbox[1]),
bbox[2],
min(85.0511, bbox[3])
]

n_dims = len(bbox) // 2
return list(transform_func(*bbox[:n_dims]) + transform_func(
Expand Down
4 changes: 2 additions & 2 deletions pygeoapi/provider/wms_facade.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(self, provider_def):

def query(self, style=None, bbox=[-180, -90, 180, 90], width=500,
height=300, crs=DEFAULT_CRS, datetime_=None, transparent=True,
bbox_crs=DEFAULT_CRS, format_='png', **kwargs):
format_='png', **kwargs):
"""
Generate map

Expand All @@ -88,7 +88,7 @@ def query(self, style=None, bbox=[-180, -90, 180, 90], width=500,

version = self.options.get('version', '1.3.0')

if version == '1.3.0' and CRS_CODES.get(bbox_crs) == 'EPSG:4326':
if version == '1.3.0' and CRS_CODES.get(crs) == 'EPSG:4326':
bbox = [bbox[1], bbox[0], bbox[3], bbox[2]]
bbox2 = ','.join(map(str, bbox))

Expand Down
62 changes: 55 additions & 7 deletions pygeoapi/templates/collections/collection.html
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ <h3>{% trans %}Storage CRS{% endtrans %}</h3>
{% block extrafoot %}
<script>
var map = L.map('collection-map').setView([{{ 0 }}, {{ 0 }}], 1);

map.addLayer(new L.TileLayer(
'{{ config['server']['map']['url'] }}', {
maxZoom: 18,
Expand All @@ -154,18 +155,65 @@ <h3>{% trans %}Storage CRS{% endtrans %}</h3>
['{{ data['extent']['spatial']['bbox'][0][1] }}', '{{ data['extent']['spatial']['bbox'][0][2] }}']
]);

{# if this collection has a map representation, add it to the map #}
{% for link in data['links'] %}
{% if link['rel'] == 'http://www.opengis.net/def/rel/ogc/1.0/map' and link['href'] %}
L.imageOverlay.ogcapi("{{ data['base_url'] }}", {collection: "{{ data['id'] }}", "opacity": .7, "transparent": true}).addTo(map);
var lbounds = bbox_layer.getBounds();

// Make sure that we pass valid coordinates to the imageOverlay
function clampLat(lat) {
return Math.max(-85.0511, Math.min(lat, 85.0511));
}

var sw = lbounds.getSouthWest();
var ne = lbounds.getNorthEast();
var clampedSw = L.latLng(clampLat(sw.lat), sw.lng);
var clampedNe = L.latLng(clampLat(ne.lat), ne.lng);
var clampedBounds = L.latLngBounds(clampedSw, clampedNe);

var ogcapi_layer = null;

{# if this collection has a map representation, add it to the map #}
{% for link in data['links'] %}
{% if link['rel'] == 'http://www.opengis.net/def/rel/ogc/1.0/map' and link['href'] %}
ogcapi_layer = L.imageOverlay.ogcapi("{{ data['base_url'] }}", {
collection: "{{ data['id'] }}",
"opacity": .7,
"transparent": true,
"bounds": clampedBounds
});
bbox_layer.setStyle({
fillOpacity: 0
});
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

// Check bounds and toggle the visibility of the imageOverlay, accordingly
function toggleOverlayVisibility() {

if (ogcapi_layer) {
var currentBounds = map.getBounds();
var west = currentBounds.getWest();
var east = currentBounds.getEast();
var centerLng = map.getCenter().lng;

var viewWidth = east - west;

var isWithinBounds = (viewWidth <= 360) && (centerLng >= -180 && centerLng <= 180);

if (isWithinBounds) {
if (!map.hasLayer(ogcapi_layer)) map.addLayer(ogcapi_layer);
} else {
if (map.hasLayer(ogcapi_layer)) map.removeLayer(ogcapi_layer);
}
}
}

map.on('moveend', toggleOverlayVisibility);

map.addLayer(bbox_layer);
map.fitBounds(bbox_layer.getBounds(), {maxZoom: 10});

map.fitBounds(clampedBounds, {maxZoom: 10});

// Run the initial visibility check
toggleOverlayVisibility();

// Allow to get bbox query parameter of a rectangular area specified by
// dragging the mouse while pressing the Ctrl key
Expand Down
21 changes: 15 additions & 6 deletions tests/provider/test_wms_facade_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,24 @@ def config():
}


def test_query(config):
def check_is_PNG(results):
assert isinstance(results, bytes)
assert results[1:4] == b'PNG'


def test_default_query(config):
p = WMSFacadeProvider(config)

results = p.query()
assert len(results) > 0
check_is_PNG(results)


def test_crs_query(config):
p = WMSFacadeProvider(config)

# an invalid CRS should return the default bbox (4326)
results2 = p.query(crs='http://www.opengis.net/def/crs/EPSG/0/1111')
assert len(results2) == len(results)
results1 = p.query(crs='http://www.opengis.net/def/crs/EPSG/0/3857')
results2 = p.query(crs='http://www.opengis.net/def/crs/EPSG/0/4326')

results3 = p.query(crs='http://www.opengis.net/def/crs/EPSG/0/3857')
assert len(results3) != len(results)
check_is_PNG(results1)
check_is_PNG(results2)
Loading