From e239a89ccc02d0de96968b7217320d2268e51102 Mon Sep 17 00:00:00 2001 From: esikkala <esko.ikkala@aalto.fi> Date: Thu, 11 Oct 2018 22:59:42 +0300 Subject: [PATCH] Add query for creation place popup --- src/client/actions/index.js | 21 +++++++++ src/client/components/map/LeafletMap.js | 58 +++++++++++++++++-------- src/client/containers/MapApp.js | 14 ++++-- src/client/epics/index.js | 15 +++++++ src/client/index.html | 3 ++ src/client/reducers/options.js | 2 +- src/client/reducers/search.js | 15 ++++++- src/server/index.js | 38 +++++++++++----- src/server/sparql/Datasets.js | 28 +++++++++--- src/server/sparql/Manuscripts.js | 8 +++- 10 files changed, 162 insertions(+), 40 deletions(-) diff --git a/src/client/actions/index.js b/src/client/actions/index.js index 199b7ab5..f4033a03 100644 --- a/src/client/actions/index.js +++ b/src/client/actions/index.js @@ -24,6 +24,11 @@ export const UPDATE_PLACES = 'UPDATE_PLACES'; export const CLEAR_PLACES = 'CLEAR_PLACES'; export const FETCH_PLACES_FAILED = 'FETCH_PLACES_FAILED'; +export const FETCH_PLACE = 'FETCH_PLACE'; +export const UPDATE_PLACE = 'UPDATE_PLACE'; +export const CLEAR_PLACE = 'CLEAR_PLACE'; +export const FETCH_PLACE_FAILED = 'FETCH_PLACE_FAILED'; + export const FETCH_FACET = 'FETCH_FACET'; export const UPDATE_FACET = 'UPDATE_FACET'; export const CLEAR_FACET = 'CLEAR_FACET'; @@ -144,6 +149,22 @@ export const fetchPlacesFailed = (error) => ({ error }); +export const fetchPlace = (placeId) => ({ + type: FETCH_PLACE, + placeId +}); +export const updatePlace = ({ place }) => ({ + type: UPDATE_PLACE, + place +}); +export const clearPlace = () => ({ + type: CLEAR_PLACES, +}); +export const fetchPlaceFailed = (error) => ({ + type: FETCH_PLACES_FAILED, + error +}); + // Facet export const fetchFacet = (property) => ({ type: FETCH_FACET, diff --git a/src/client/components/map/LeafletMap.js b/src/client/components/map/LeafletMap.js index df977609..1a7074e5 100644 --- a/src/client/components/map/LeafletMap.js +++ b/src/client/components/map/LeafletMap.js @@ -104,7 +104,7 @@ class LeafletMap extends React.Component { } - componentDidUpdate({ results, mapMode, geoJSONKey, bouncingMarkerKey, openPopupMarkerKey }) { + componentDidUpdate({ results, place, mapMode, geoJSONKey, bouncingMarkerKey, openPopupMarkerKey }) { if (this.props.bouncingMarker === '' && this.bouncingMarkerObj !== null) { this.leafletMap.removeLayer(this.bouncingMarkerObj); } @@ -142,6 +142,17 @@ class LeafletMap extends React.Component { } } + if (this.props.place !== place) { + this.markers[this.props.place.id.replace('http://ldf.fi/mmm/place/', '')] + .bindPopup(this.createPopUpContent(this.props.place), { + maxHeight: 300, + maxWidth: 350, + minWidth: 350, + //closeButton: false, + }) + .openPopup(); + } + // check if geoJSON has updated if (this.props.geoJSONKey !== geoJSONKey) { this.props.geoJSON.map(obj => { @@ -158,7 +169,7 @@ class LeafletMap extends React.Component { this.markers = {}; Object.values(results).forEach(value => { const marker = this.createMarker(value); - this.markers[value.id] = marker; + this.markers[value.id.replace('http://ldf.fi/mmm/place/', '')] = marker; marker == null ? null : marker.addTo(this.resultMarkerLayer); }); } @@ -184,10 +195,9 @@ class LeafletMap extends React.Component { return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) }); } }); - // const clusterer = L.markerClusterGroup(); - Object.values(results).forEach(value => { + results.forEach(value => { const marker = this.createMarker(value); - this.markers[value.id] = marker; + this.markers[value.id.replace('http://ldf.fi/mmm/place/', '')] = marker; marker == null ? null : clusterer.addLayer(marker); }); clusterer.addTo(this.resultMarkerLayer); @@ -212,30 +222,42 @@ class LeafletMap extends React.Component { const marker = L.marker(latLng, { icon: icon, manuscriptCount: result.manuscriptCount ? result.manuscriptCount : null, + id: result.id }) - .bindPopup(this.createPopUpContent(result), { maxHeight: 300, maxWidth: 350, minWidth: 300 }); + .on('click', this.markerOnClick); return marker; } } + markerOnClick = (event) => { + const placeId = (event.target.options.id.replace('http://ldf.fi/mmm/place/', '')); + this.props.fetchPlace(placeId); + }; + createPopUpContent(result) { - let popUpTemplate = ` - <h3><a target="_blank" rel="noopener noreferrer" href={sdbmLink}>{prefLabel}</a></p></h3> - <p>Number of manuscripts created here: {manuscriptCount}</p> - `; + // console.log(result) + let popUpTemplate = `<h3><a target="_blank" rel="noopener noreferrer" href=${result.sdbmLink}>${result.prefLabel}</a></p></h3>`; if (has(result, 'source')) { - popUpTemplate += '<p>Place authority: <a target="_blank" rel="noopener noreferrer" href={source}>{source}</a></p>'; + popUpTemplate += `<p>Place authority: <a target="_blank" rel="noopener noreferrer" href=${result.source}>${result.source}</a></p>`; } - //popUpTemplate += this.createManscriptListing(result.manuscript); - return L.Util.template(popUpTemplate, result); + popUpTemplate += `<p>Manuscripts created here:</p>`; + popUpTemplate += this.createManscriptListing(result.manuscript); + return popUpTemplate; } createManscriptListing(manuscripts) { let html = ''; - manuscripts.forEach(msId => { - const sdbmLink = msId.replace('http://ldf.fi/mmm/manifestation_singleton/', 'https://sdbm.library.upenn.edu/manuscripts/'); + if (Array.isArray(manuscripts)) { + html += '<ul>'; + manuscripts.forEach(ms => { + let sdbmLink = has(ms, 'manuscriptRecord') ? ms.manuscriptRecord : ms.entry; + html += '<li><a target="_blank" rel="noopener noreferrer" href=' + sdbmLink + '>' + sdbmLink + '</a></li>'; + }); + html += '</ul>'; + } else { + let sdbmLink = has(manuscripts, 'manuscriptRecord') ? manuscripts.manuscriptRecord : manuscripts.entry; html += '<p><a target="_blank" rel="noopener noreferrer" href=' + sdbmLink + '>' + sdbmLink + '</a></p>'; - }); + } return html; } @@ -265,8 +287,10 @@ class LeafletMap extends React.Component { LeafletMap.propTypes = { fetchPlaces: PropTypes.func.isRequired, + fetchPlace: PropTypes.func.isRequired, fetchManuscripts: PropTypes.func.isRequired, - results: PropTypes.array, + results: PropTypes.array.isRequired, + place: PropTypes.object.isRequired, mapMode: PropTypes.string.isRequired, geoJSON: PropTypes.array, geoJSONKey: PropTypes.number.isRequired, diff --git a/src/client/containers/MapApp.js b/src/client/containers/MapApp.js index 9c701fd5..7276e0f6 100644 --- a/src/client/containers/MapApp.js +++ b/src/client/containers/MapApp.js @@ -13,7 +13,7 @@ import GMap from '../components/map/GMap'; import Pie from '../components/Pie'; import TopBar from '../components/TopBar'; import CircularProgress from '@material-ui/core/CircularProgress'; -import Typography from '@material-ui/core/Typography'; +//import Typography from '@material-ui/core/Typography'; import purple from '@material-ui/core/colors/purple'; import { @@ -28,6 +28,7 @@ import { clearSuggestions, fetchManuscripts, fetchPlaces, + fetchPlace, fetchFacet, fetchResults, clearManuscripts, @@ -144,12 +145,13 @@ const styles = theme => ({ }); let MapApp = (props) => { - const { classes, options, browser, search, map, manuscripts, creationPlaces, facet } = props; + const { classes, options, browser, search, map, manuscripts, creationPlaces, place, facet } = props; //error, let oneColumnView = true; //console.log(manuscripts) + //console.log(place) let table = ''; if (search.fetchingManuscripts) { @@ -196,8 +198,10 @@ let MapApp = (props) => { mapElement = ( <LeafletMap fetchPlaces={props.fetchPlaces} + fetchPlace={props.fetchPlace} fetchManuscripts={props.fetchManuscripts} results={creationPlaces} + place={place} mapMode={options.mapMode} geoJSON={map.geoJSON} geoJSONKey={map.geoJSONKey} @@ -294,6 +298,7 @@ const mapStateToProps = (state) => { manuscripts: getVisibleResults(state.search), manuscriptsPropertyValues: getVisibleValues(state.search), creationPlaces: state.search.places, + place: state.search.place, facet: state.facet, results: state.search.results, page: state.search.page @@ -307,6 +312,7 @@ const mapDispatchToProps = ({ clearSuggestions, fetchManuscripts, fetchPlaces, + fetchPlace, fetchResults, fetchFacet, clearManuscripts, @@ -332,7 +338,8 @@ MapApp.propTypes = { search: PropTypes.object.isRequired, map: PropTypes.object.isRequired, manuscripts: PropTypes.array, - creationPlaces: PropTypes.array, + creationPlaces: PropTypes.array.isRequired, + place: PropTypes.object.isRequired, manuscriptsPropertyValues: PropTypes.object.isRequired, facet: PropTypes.object.isRequired, results: PropTypes.number.isRequired, @@ -344,6 +351,7 @@ MapApp.propTypes = { clearSuggestions: PropTypes.func.isRequired, fetchManuscripts: PropTypes.func.isRequired, fetchPlaces: PropTypes.func.isRequired, + fetchPlace: PropTypes.func.isRequired, fetchFacet: PropTypes.func.isRequired, fetchResults: PropTypes.func.isRequired, clearManuscripts: PropTypes.func.isRequired, diff --git a/src/client/epics/index.js b/src/client/epics/index.js index ffeb5e6b..81be3f98 100644 --- a/src/client/epics/index.js +++ b/src/client/epics/index.js @@ -5,6 +5,7 @@ import { combineEpics, ofType } from 'redux-observable'; import { updateManuscripts, updatePlaces, + updatePlace, updateFacet, updateResults, //updateGeoJSON, @@ -13,6 +14,7 @@ import { FETCH_MANUSCRIPTS, //FETCH_MANUSCRIPTS_FAILED, FETCH_PLACES, + FETCH_PLACE, FETCH_RESULTS //FETCH_PLACES_FAILED, //GET_GEOJSON, @@ -45,6 +47,18 @@ const getPlaces = action$ => action$.pipe( }) ); +const getPlace = action$ => action$.pipe( + ofType(FETCH_PLACE), + mergeMap(action => { + console.log(action.placeId) + const searchUrl = hiplaApiUrl + 'places'; + const requestUrl = `${searchUrl}/${action.placeId}`; + return ajax.getJSON(requestUrl).pipe( + map(response => updatePlace({ place: response })) + ); + }) +); + const getFacet = action$ => action$.pipe( ofType(FETCH_FACET), mergeMap((action) => { @@ -92,6 +106,7 @@ const getResultCount = action$ => action$.pipe( const rootEpic = combineEpics( getManuscripts, getPlaces, + getPlace, getFacet, getResultCount, // getGeoJSONEpic diff --git a/src/client/index.html b/src/client/index.html index 29b15282..bea0df84 100644 --- a/src/client/index.html +++ b/src/client/index.html @@ -29,6 +29,9 @@ .rstcustom__expandButton { margin-top: 1px } + .leaflet-popup-scrolled { + border: 0px; + } </style> <title><%= htmlWebpackPlugin.options.title %></title> </head> diff --git a/src/client/reducers/options.js b/src/client/reducers/options.js index 217020ce..7cf65d56 100644 --- a/src/client/reducers/options.js +++ b/src/client/reducers/options.js @@ -5,7 +5,7 @@ import { } from '../actions'; const DEFAULT_LANGUAGE = 'en'; -const DEFAULT_RESULT_FORMAT = 'table'; +const DEFAULT_RESULT_FORMAT = 'map'; const DEFAULT_MAP_MODE = 'cluster'; export const INITIAL_STATE = { diff --git a/src/client/reducers/search.js b/src/client/reducers/search.js index 3996ce1a..6e2ece6f 100644 --- a/src/client/reducers/search.js +++ b/src/client/reducers/search.js @@ -6,13 +6,14 @@ import { CLEAR_SUGGESTIONS, FETCH_RESULTS, UPDATE_RESULTS, - CLEAR_RESULTS, FETCH_MANUSCRIPTS, FETCH_PLACES, + FETCH_PLACE, UPDATE_MANUSCRIPTS, CLEAR_MANUSCRIPTS, UPDATE_PLACES, CLEAR_PLACES, + UPDATE_PLACE, UPDATE_RESULTS_FILTER, SORT_RESULTS } from '../actions'; @@ -41,6 +42,7 @@ export const INITIAL_STATE = { manuscripts: [], page: 0, places: [], + place: {}, manuscriptsFilter: { //'author': new Set(), //'timespan': new Set(), @@ -79,6 +81,8 @@ const search = (state = INITIAL_STATE, action) => { return { ...state, fetchingManuscripts: true }; case FETCH_PLACES: return { ...state, fetchingPlaces: true }; + case FETCH_PLACE: + return { ...state, fetchingPlaces: true }; case FETCH_RESULTS: return { ...state, fetchResults: true }; case CLEAR_SUGGESTIONS: @@ -112,7 +116,12 @@ const search = (state = INITIAL_STATE, action) => { return { ...state, places: action.places, - //resultsQuery: state.query, + fetchingPlaces: false + }; + case UPDATE_PLACE: + return { + ...state, + place: action.place, fetchingPlaces: false }; case CLEAR_MANUSCRIPTS: @@ -129,6 +138,8 @@ const search = (state = INITIAL_STATE, action) => { resultsQuery: '', fetchingPlaces: false }; + + case UPDATE_RESULTS_FILTER: return updateResultsFilter(state, action); case SORT_RESULTS: diff --git a/src/server/index.js b/src/server/index.js index 67ecba41..8c839489 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,8 +1,13 @@ import express from 'express'; import bodyParser from 'body-parser'; import request from 'superagent'; -import _ from 'lodash'; -import { getManuscripts, getManuscriptCount, getPlaces, getFacet } from './sparql/Manuscripts'; +import { + getManuscripts, + getManuscriptCount, + getPlaces, + getPlace, + getFacet +} from './sparql/Manuscripts'; const DEFAULT_PORT = 3001; const app = express(); //const isDevelopment = app.get('env') !== 'production'; @@ -43,15 +48,26 @@ app.get('/manuscript-count', (req, res) => { }); }); -app.get('/places', (req, res) => { - return getPlaces().then((data) => { - // console.log(data); - res.json(data); - }) - .catch((err) => { - console.log(err); - return res.sendStatus(500); - }); +app.get('/places/:placeId?', (req, res) => { + if (req.params.placeId) { + return getPlace(req.params.placeId).then(data => { + // console.log(data) + res.json(data[0]); + }) + .catch((err) => { + console.log(err); + return res.sendStatus(500); + }); + } else { + return getPlaces().then((data) => { + // console.log(data); + res.json(data); + }) + .catch((err) => { + console.log(err); + return res.sendStatus(500); + }); + } }); app.get('/facet', (req, res) => { diff --git a/src/server/sparql/Datasets.js b/src/server/sparql/Datasets.js index 6a236e1e..b62125ee 100644 --- a/src/server/sparql/Datasets.js +++ b/src/server/sparql/Datasets.js @@ -55,6 +55,8 @@ module.exports = { } FILTER(BOUND(?id)) ?id skos:prefLabel ?prefLabel . + ?id mmm-schema:entry ?entry . + OPTIONAL { ?id mmm-schema:manuscript_record ?manuscriptRecord . } OPTIONAL { ?id crm:P45_consists_of ?material . } ?expression_creation frbroo:R18_created ?id . OPTIONAL { @@ -78,7 +80,6 @@ module.exports = { ?id crm:P128_carries ?expression . ?expression crm:P72_has_language ?language . } - OPTIONAL { ?id mmm-schema:manuscript_record ?manuscriptRecord . } } `, 'allQuery': ` @@ -130,7 +131,7 @@ module.exports = { ORDER BY (!BOUND(?creationPlace)) ?creationPlace <PAGE> `, - 'placeQuery': ` + 'placesQuery': ` PREFIX skos: <http://www.w3.org/2004/02/skos/core#> PREFIX wgs84: <http://www.w3.org/2003/01/geo/wgs84_pos#> PREFIX dc: <http://purl.org/dc/elements/1.1/> @@ -138,7 +139,7 @@ module.exports = { PREFIX frbroo: <http://erlangen-crm.org/efrbroo/> PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/> PREFIX mmm-schema: <http://ldf.fi/mmm/schema/> - SELECT ?id ?prefLabel ?lat ?long ?source ?parent ?sdbmLink + SELECT ?id ?lat ?long ?prefLabel (COUNT(DISTINCT ?manuscript) as ?manuscriptCount) WHERE { ?id a mmm-schema:Place . @@ -148,11 +149,28 @@ module.exports = { ?id wgs84:lat ?lat ; wgs84:long ?long . } + } + GROUP BY ?id ?lat ?long ?prefLabel + `, + 'placeQuery': ` + PREFIX skos: <http://www.w3.org/2004/02/skos/core#> + PREFIX wgs84: <http://www.w3.org/2003/01/geo/wgs84_pos#> + PREFIX dc: <http://purl.org/dc/elements/1.1/> + PREFIX owl: <http://www.w3.org/2002/07/owl#> + PREFIX frbroo: <http://erlangen-crm.org/efrbroo/> + PREFIX crm: <http://www.cidoc-crm.org/cidoc-crm/> + PREFIX mmm-schema: <http://ldf.fi/mmm/schema/> + SELECT ?id ?prefLabel ?manuscript__id ?manuscript__entry ?manuscript__manuscriptRecord ?sdbmLink ?source ?parent + WHERE { + BIND (<PLACE_ID> AS ?id) + BIND(REPLACE(STR(?id), "http://ldf.fi/mmm/place/", "https://sdbm.library.upenn.edu/places/") AS ?sdbmLink) + ?id skos:prefLabel ?prefLabel . + ?manuscript__id ^frbroo:R18_created/crm:P7_took_place_at ?id . + ?manuscript__id mmm-schema:entry ?manuscript__entry . + OPTIONAL { ?manuscript__id mmm-schema:manuscript_record ?manuscript__manuscriptRecord } OPTIONAL { ?id owl:sameAs ?source . } OPTIONAL { ?id mmm-schema:parent ?parent } - BIND(REPLACE(STR(?id), "http://ldf.fi/mmm/place/", "https://sdbm.library.upenn.edu/places/") AS ?sdbmLink) } - GROUP BY ?id ?prefLabel ?lat ?long ?source ?parent ?sdbmLink `, 'tgn': { // Getty LOD documentation: diff --git a/src/server/sparql/Manuscripts.js b/src/server/sparql/Manuscripts.js index eeb1a11a..83397b81 100644 --- a/src/server/sparql/Manuscripts.js +++ b/src/server/sparql/Manuscripts.js @@ -22,7 +22,13 @@ export const getManuscriptCount = () => { }; export const getPlaces = () => { - const { endpoint, placeQuery } = datasetConfig['mmm']; + const { endpoint, placesQuery } = datasetConfig['mmm']; + return sparqlSearchEngine.doSearch(placesQuery, endpoint, makeObjectList); +}; + +export const getPlace = (id) => { + let { endpoint, placeQuery } = datasetConfig['mmm']; + placeQuery = placeQuery.replace('<PLACE_ID>', `<http://ldf.fi/mmm/place/${id}>`); return sparqlSearchEngine.doSearch(placeQuery, endpoint, makeObjectList); }; -- GitLab