Skip to content

Commit 0bd93ae

Browse files
committed
Geospatial maps: Review feedback addressed
* Documentation of config * Set bounding box to use dcterms.spatial by default * Uncomment geospatial field and wrap in config test * New config to 'enable' inclusion of geospatial field * Docs for map and marker detail models * Bounding box validation * Fixed view mode control flow * Tidied console output
1 parent 6fd36bd commit 0bd93ae

13 files changed

Lines changed: 97 additions & 40 deletions

File tree

config/config.example.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,11 +554,27 @@ liveRegion:
554554

555555
# Geospatial Map display options
556556
geospatialMapViewer:
557+
# Which fields to use for parsing as geospatial points in search maps
558+
# (note, the item page field component allows any field(s) to be used
559+
# and is set as an input when declaring the component)
557560
spatialMetadataFields:
558561
- 'dcterms.spatial'
562+
# Which discovery configuration to use for 'geospatial search', used
563+
# in the browse map
559564
spatialFacetDiscoveryConfiguration: 'geospatial'
565+
# Which filter / facet name to use for faceted geospatial search
566+
# used in the browse map
560567
spatialPointFilterName: 'point'
568+
# Whether item page geospatial metadata should be displayed
569+
# (assumes they are wrapped in a test for this config in the template as
570+
# per the default templates supplied with DSpace for untyped-item and publication)
571+
enableItemPageFields: false
572+
# Whether the browse map should be enabled and included in the browse menu
561573
enableBrowseMap: false
574+
# Whether a 'map view' mode should be included alongside list and grid views
575+
# in search result pages
562576
enableSearchViewMode: false
577+
# The tile provider(s) to use for the map tiles drawn in the leaflet maps.
578+
# (see https://leaflet-extras.github.io/leaflet-providers/preview/) for a full list
563579
tileProviders:
564580
- 'OpenStreetMap.Mapnik'

src/app/browse-by/browse-by-geospatial-data/browse-by-geospatial-data.component.spec.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,6 @@ describe('BrowseByGeospatialDataComponent', () => {
102102
it('component should be created successfully', () => {
103103
expect(component).toBeTruthy();
104104
});
105-
// return this.searchService.getFacetValuesFor(searchFilterConfig, 1, searchOptions,
106-
// null, true);
107105
describe('BrowseByGeospatialDataComponent component with valid facet values', () => {
108106
beforeEach(() => {
109107
fixture = TestBed.createComponent(BrowseByGeospatialDataComponent);

src/app/item-page/simple/field-components/specific-field/geospatial/geospatial-item-page-field.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class GeospatialItemPageFieldComponent extends ItemPageFieldComponent imp
5151
/**
5252
* List of fields to parse for bounding box GeoJSON
5353
*/
54-
@Input() bboxFields = ['gnd.coordinates.bbox'];
54+
@Input() bboxFields = ['dcterms.spatial'];
5555

5656
/**
5757
* Whether to cluster markers into groups

src/app/item-page/simple/item-types/publication/publication.component.html

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,15 @@
106106
[fields]="['datacite.relation.isReferencedBy']"
107107
[label]="'item.page.referenced'">
108108
</ds-item-page-uri-field>
109-
<!-- Below is an example of how to render one or more lat/lng points and/or bounding box rectangles
110-
in a tiled map viewer. Set 'cluster' to true for marker clustering -->
111-
<!-- <ds-geospatial-item-page-field [item]="object"-->
112-
<!-- [label]="'item.page.places'"-->
113-
<!-- [pointFields]="['dcterms.spatial']"-->
114-
<!-- [bboxFields]="['external.spatial.bbox']"-->
115-
<!-- [cluster]="true"-->
116-
<!-- >-->
117-
<!-- </ds-geospatial-item-page-field>-->
109+
@if (geospatialItemPageFieldsEnabled) {
110+
<ds-geospatial-item-page-field [item]="object"
111+
[label]="'item.page.places'"
112+
[pointFields]="['dcterms.spatial']"
113+
[bboxFields]="['dcterms.spatial']"
114+
[cluster]="true"
115+
>
116+
</ds-geospatial-item-page-field>
117+
}
118118
<div>
119119
<a class="btn btn-outline-primary" role="button" [routerLink]="[itemPageRoute + '/full']">
120120
<i class="fas fa-info-circle"></i> {{"item.page.link.full" | translate}}

src/app/item-page/simple/item-types/shared/item.component.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,15 @@ export class ItemComponent implements OnInit {
7575

7676
mediaViewer;
7777

78+
/**
79+
* Enables display of geospatial item page fields
80+
*/
81+
geospatialItemPageFieldsEnabled = false;
82+
7883
constructor(protected routeService: RouteService,
7984
protected router: Router) {
8085
this.mediaViewer = environment.mediaViewer;
86+
this.geospatialItemPageFieldsEnabled = environment.geospatialMapViewer.enableItemPageFields;
8187
}
8288

8389
/**

src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,15 @@
7171
[fields]="['dc.identifier.citation']"
7272
[label]="'item.page.citation'">
7373
</ds-generic-item-page-field>
74-
<!-- Below is an example of how to render one or more lat/lng points and/or bounding box rectangles
75-
in a tiled map viewer. Set 'cluster' to true for marker clustering -->
76-
<!-- <ds-geospatial-item-page-field [item]="object"-->
77-
<!-- [label]="'item.page.places'"-->
78-
<!-- [pointFields]="['dcterms.spatial']"-->
79-
<!-- [bboxFields]="['external.spatial.bbox']"-->
80-
<!-- [cluster]="true"-->
81-
<!-- >-->
82-
<!-- </ds-geospatial-item-page-field>-->
74+
@if (geospatialItemPageFieldsEnabled) {
75+
<ds-geospatial-item-page-field [item]="object"
76+
[label]="'item.page.places'"
77+
[pointFields]="['dcterms.spatial']"
78+
[bboxFields]="['dcterms.spatial']"
79+
[cluster]="true"
80+
>
81+
</ds-geospatial-item-page-field>
82+
}
8383
<ds-item-page-uri-field [item]="object"
8484
[fields]="['dc.identifier.uri']"
8585
[label]="'item.page.uri'">

src/app/shared/geospatial-map/geospatial-map.component.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export class GeospatialMapComponent implements AfterViewInit, OnInit, OnDestroy
213213

214214
// Map bounds / zoom fitting tends to be smoother when done after a short delay
215215
setTimeout(() => {
216-
if (isNotEmpty(this.parsedBoundingBoxes) && this.coordinates.length === 1) {
216+
if (bboxBounds && this.coordinates.length === 1) {
217217
// One point, at least one bbox, use its bounds. Otherwise, use the calculation based on points.
218218
bounds = bboxBounds;
219219
}
@@ -315,12 +315,34 @@ export class GeospatialMapComponent implements AfterViewInit, OnInit, OnDestroy
315315
* @param bbox
316316
* @private
317317
*/
318-
private renderBoundingBox(L, bbox: string): string[][] {
318+
private renderBoundingBox(L, bbox: string): number[][] {
319319
if (hasValue(bbox)) {
320320
let parsedBbox = bbox.replace(/[{} ]/g, '');
321321
parsedBbox = parsedBbox.replace(/[^=,]+=/g, '');
322-
const parsedBboxParts = parsedBbox.split(',');
323-
const bounds = [[parsedBboxParts[1], parsedBboxParts[0]], [parsedBboxParts[2], parsedBboxParts[3]]];
322+
const parsedBboxParts = parsedBbox.split(',', 4);
323+
// Validate that we have exactly 4 parts
324+
if (parsedBboxParts.length !== 4) {
325+
console.error('Invalid bounding box format: expected 4 coordinates but got ' + parsedBboxParts.length);
326+
return;
327+
}
328+
// Convert to numbers and validate
329+
const coordinates = parsedBboxParts.map(part => parseFloat(part));
330+
if (coordinates.some(isNaN)) {
331+
console.error('Invalid bounding box: contains non-numeric values', parsedBboxParts);
332+
return;
333+
}
334+
// Create bounds array with proper structure [[lat1, lng1], [lat2, lng2]]
335+
const bounds = [[coordinates[1], coordinates[0]], [coordinates[2], coordinates[3]]];
336+
// Validate latitude values (-90 to 90)
337+
if (bounds[0][0] < -90 || bounds[0][0] > 90 || bounds[1][0] < -90 || bounds[1][0] > 90) {
338+
console.error('Invalid bounding box: latitude values must be between -90 and 90', bounds);
339+
return;
340+
}
341+
// Validate longitude values (-180 to 180)
342+
if (bounds[0][1] < -180 || bounds[0][1] > 180 || bounds[1][1] < -180 || bounds[1][1] > 180) {
343+
console.error('Invalid bounding box: longitude values must be between -180 and 180', bounds);
344+
return;
345+
}
324346
const boundingBox = L.rectangle(bounds, { color: '#5574BB', weight: 2, fill: false });
325347
this.map.addLayer(boundingBox);
326348
// return bounds so the map can be centred / zoomed appropriately

src/app/shared/geospatial-map/models/geospatial-map-detail.model.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { GeospatialMarkerDetail } from './geospatial-marker-detail.model';
22

3+
/**
4+
* This class is used by the ObjectGeospatialMap list view, to supply point data as well as
5+
* routing and title data for the DSO associated with the geospatial points.
6+
*/
37
export class GeospatialMapDetail {
48
points: GeospatialMarkerDetail[] = [];
59
route: string;

src/app/shared/geospatial-map/models/geospatial-marker-detail.model.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
/**
2+
* This class is used by GeospatialMapDetail, to keep richer data for a list of points
3+
* so that they can be rendered as links and alt-text or hover text when drawn on a map
4+
*/
15
export class GeospatialMarkerDetail {
26
latitude: number;
37
longitude: number;

src/app/shared/view-mode-switch/view-mode-switch.component.html

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,20 @@
4040
[attr.data-test]="'detail-view' | dsBrowserOnly">
4141
<i class="far fa-square"></i>
4242
</button>
43-
@if (isToShow(viewModeEnum.GeospatialMap)) {
44-
<button
45-
[attr.aria-current]="currentMode === viewModeEnum.GeospatialMap"
46-
[attr.aria-label]="'search.view-switch.show-geospatialMap' | translate"
47-
routerLink="."
48-
[queryParams]="{view: 'geospatialMap'}"
49-
queryParamsHandling="merge"
50-
(click)="switchViewTo(viewModeEnum.GeospatialMap)"
51-
routerLinkActive="active"
52-
[class.active]="currentMode === viewModeEnum.GeospatialMap"
53-
class="btn btn-secondary"
54-
[attr.data-test]="'geospatial-view' | dsBrowserOnly">
55-
<span class="fas fa-map"></span><span class="sr-only">{{'search.view-switch.show-geospatialMap' | translate}}</span>
56-
</button>
57-
}
43+
}
44+
@if (isToShow(viewModeEnum.GeospatialMap)) {
45+
<button
46+
[attr.aria-current]="currentMode === viewModeEnum.GeospatialMap"
47+
[attr.aria-label]="'search.view-switch.show-geospatialMap' | translate"
48+
routerLink="."
49+
[queryParams]="{view: 'geospatialMap'}"
50+
queryParamsHandling="merge"
51+
(click)="switchViewTo(viewModeEnum.GeospatialMap)"
52+
routerLinkActive="active"
53+
[class.active]="currentMode === viewModeEnum.GeospatialMap"
54+
class="btn btn-secondary"
55+
[attr.data-test]="'geospatial-view' | dsBrowserOnly">
56+
<span class="fas fa-map"></span><span class="sr-only">{{'search.view-switch.show-geospatialMap' | translate}}</span>
57+
</button>
5858
}
5959
</div>

0 commit comments

Comments
 (0)