Skip to content

feat: GeoJSONViewer for map subjects#7442

Open
eatyourgreens wants to merge 4 commits intozooniverse:mainfrom
eatyourgreens:feat-geojson-viewer
Open

feat: GeoJSONViewer for map subjects#7442
eatyourgreens wants to merge 4 commits intozooniverse:mainfrom
eatyourgreens:feat-geojson-viewer

Conversation

@eatyourgreens
Copy link
Copy Markdown
Contributor

@eatyourgreens eatyourgreens commented Apr 4, 2026

A GeoJSONViewer component that displays GeoJSON subjects as an embedded OpenLayers map, with the subject reference data beneath.

Supports .geojson files with the application/geo+json MIME type, and falls back to sniffing file content for Feature or FeatureCollection for generic JSON files.

Builds the map with these react-openlayers components:

Examples

Talk subject page showing an Open Street Map map for a GeoJSON subject, with text metadata about the location under the map. As the previous image, but this shows the map on a Talk subject page. Similar to the previous picture, but this map also has a red, dashed uncertainty circle. A map thumbnail in the discussion listing for the Notes board, showing a smaller area of the previous map, centred on the same location. A similar small map thumbnail, but this time for a search result in the Talk search listing.
  • Small screen (phones):
Subject discussion notes on a small screen, with a square map and reference data listed beneath.

Required Manual Testing

  • Does the non-logged in home page render correctly?
  • Does the logged in home page render correctly?
  • Does the projects page render correctly?
  • Can you load project home pages?
  • Can you load the classification page?
  • Can you submit a classification?
  • Does talk load correctly?
  • Can you post a talk comment?

Review Checklist

  • Does it work in all major browsers: Firefox, Chrome, Edge, Safari?
  • Does it work on mobile?
  • Can you npm ci and app works as expected?
  • If the component is in coffeescript, is it converted to ES6? Is it free of eslint errors? Is the conversion its own commit?
  • Are the tests passing locally and on GitHub Actions?

Optional

Copilot AI review requested due to automatic review settings April 4, 2026 14:46
@eatyourgreens
Copy link
Copy Markdown
Contributor Author

@mcbouslog I don't know if this will be useful, but a map seems more accessible/usable in Talk than the raw subject JSON.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new GeoJSONViewer to render GeoJSON subjects with an embedded OpenLayers map and show reference_data beneath, plus wiring/styling and a new ol dependency.

Changes:

  • Add OpenLayers (ol) dependency for map rendering.
  • Add GeoJSONViewer component and integrate it into the file viewer selection / JSON inspection flow.
  • Add Stylus styles for the GeoJSON viewer and include them in the main CSS bundle.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
package.json Adds ol dependency needed for OpenLayers rendering.
package-lock.json Locks ol and its transitive dependencies.
css/main.styl Includes the new GeoJSON viewer stylesheet.
css/geojson-viewer.styl Adds styles for OpenLayers controls and reference data layout.
app/components/file-viewer/json-container.jsx Routes detected GeoJSON JSON payloads to GeoJSONViewer.
app/components/file-viewer/index.jsx Attempts to select GeoJSONViewer for application/geo+json format.
app/components/file-viewer/geojson-viewer.jsx New OpenLayers-based GeoJSON map viewer implementation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/components/file-viewer/index.jsx Outdated
Comment thread app/components/file-viewer/json-container.jsx Outdated
Comment thread app/components/file-viewer/geojson-viewer.jsx
Comment thread app/components/file-viewer/geojson-viewer.jsx Outdated
Comment thread app/components/file-viewer/geojson-viewer.jsx Outdated
Comment thread app/components/file-viewer/geojson-viewer.jsx Outdated
Comment thread css/geojson-viewer.styl Outdated
@coveralls
Copy link
Copy Markdown

coveralls commented Apr 4, 2026

Coverage Status

coverage: 55.771% (+0.1%) from 55.649% — eatyourgreens:feat-geojson-viewer into zooniverse:main

@eatyourgreens eatyourgreens force-pushed the feat-geojson-viewer branch 7 times, most recently from bbc59a3 to 39e628b Compare April 5, 2026 01:34
let vectorSource;
let vectorLayer;
try {
vectorSource = new VectorSource({ useSpatialIndex: false });
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This errors with a stack overflow (too much recursion) in RBush, even though spatial indexing is disabled, and I can't see why.

Copy link
Copy Markdown
Contributor Author

@eatyourgreens eatyourgreens Apr 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was an error in the webpack config, only triggered when you have two modules in the tree with ambiguous names (in this case RBush.js and rbush.js on case-insensitive MacOS.)

Fixed in this PR by adding stricter module resolution rules to the webpack configs.

@eatyourgreens eatyourgreens force-pushed the feat-geojson-viewer branch 16 times, most recently from 3af916b to 831e774 Compare April 5, 2026 22:24
@eatyourgreens eatyourgreens force-pushed the feat-geojson-viewer branch 4 times, most recently from f4850e8 to 3d0e952 Compare April 6, 2026 09:00
@eatyourgreens eatyourgreens requested a review from Copilot April 6, 2026 09:27
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 8 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/components/file-viewer/json-container.jsx
Comment thread app/components/file-viewer/geojson-viewer.jsx
Comment thread webpack.dev.js Outdated
Comment thread css/geojson-viewer.styl Outdated
Comment on lines +55 to +57
// If detected as GeoJSON (or JSON with type === 'Feature' or 'FeatureCollection'), use GeoJSONViewer
if (props.format === 'geo+json' || jsonData?.type === 'Feature' || jsonData?.type === 'FeatureCollection') {
return <GeoJSONViewer {...props} jsonData={jsonData} />;
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are existing component tests under app/components/file-viewer (e.g. canvas-viewer.spec.jsx), but the new GeoJSON rendering path isn’t covered. Please add tests that verify GeoJSON detection/routing and that the viewer renders a map + reference data for a Feature/FeatureCollection payload.

Copilot uses AI. Check for mistakes.
Comment thread app/components/file-viewer/geojson-viewer.jsx Outdated
Comment on lines +75 to +79
if (extent) {
view.fit(extent, {
padding: [100, 100, 100, 100],
maxZoom: 12,
});
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

view.fit(...) is being called during render, which will re-fit whenever this component re-renders (e.g. parent updates), preventing users from panning/zooming. Move the fit call into a useEffect that runs when the map becomes available / when features change, and guard it with a non-empty extent check (empty VectorSource#getExtent() can still be truthy).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FittedVectorLayer only re-renders when features changes, so this should be fine.

Comment thread webpack.dev.js
module: {
rules: [{
test: /\.m?js$/,
include: /node_modules\/react-openlayers\/dist/,
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This include regex hard-codes POSIX path separators (/node_modules/.../dist/), so the rule won’t match on Windows where paths use \\. Use a cross-platform pattern (e.g. [\\/]node_modules[\\/]react-openlayers[\\/]dist[\\/]) or compute an absolute include path with path.dirname(require.resolve(...)).

Suggested change
include: /node_modules\/react-openlayers\/dist/,
include: /[\\/]node_modules[\\/]react-openlayers[\\/]dist[\\/]/,

Copilot uses AI. Check for mistakes.
Comment thread webpack.build.js
module: {
rules: [{
test: /\.m?js$/,
include: /node_modules\/react-openlayers\/dist/,
Copy link

Copilot AI Apr 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This include regex hard-codes POSIX path separators (/node_modules/.../dist/), so the rule won’t match on Windows where paths use \\. Use a cross-platform pattern (e.g. [\\/]node_modules[\\/]react-openlayers[\\/]dist[\\/]) or compute an absolute include path with path.dirname(require.resolve(...)).

Suggested change
include: /node_modules\/react-openlayers\/dist/,
include: /[\\/]node_modules[\\/]react-openlayers[\\/]dist[\\/]/,

Copilot uses AI. Check for mistakes.
Comment thread app/components/file-viewer/geojson-viewer.jsx
Comment thread test/mocha-preload.js
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ol is ESM, so mocha needs a custom loader to resolve partial specifiers like ol/layer/Vector to their fully-specified paths: ol/layer/Vector.js.

@mcbouslog
Copy link
Copy Markdown
Contributor

@eatyourgreens - this is awesome, thank you!! Let's please keep this open to revisit with parity in FEM and use as a reference, but I'm not sure if we'll review/merge in the short-term. Just letting you know timing. I can/will help with merge conflicts.

const features = useMemo(() => geoJsonFormat.readFeatures(jsonData, {
dataProjection: 'EPSG:4326', // incoming data is WGS 84.
featureProjection: 'EPSG:3857', // rendered map is Web Mercator.
featureClass: RenderFeature // render read-only features on the map.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcbouslog this is kind of hidden away in the documentation, but OpenLayers exports a RenderFeature class for cases where you want to show features but don't need them to be interactive eg. read-only locations.

@eatyourgreens
Copy link
Copy Markdown
Contributor Author

eatyourgreens commented Apr 9, 2026

@mcbouslog no worries. It occurred to me that the first thing volunteers will ask for, on mapping projects, is to see the maps in Talk.

A `GeoJSONViewer` component that displays GeoJSON subjects as an embedded OpenLayers map, with the subject reference data beneath.

Supports `.geojson` files with the `application/geo+json` MIME type, and falls back to the default JSON viewer for other JSON files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants