Skip to content

Commit

Permalink
Task/WG-5: complete leaflet map (#284)
Browse files Browse the repository at this point in the history
* Add zoom-to-feature on feature selection

* Do some performance optimizations

* Refactor zoom bahvior into own file

* Add display of geosjson features and point clouds to map

* Refactor selecting feature to be a hook

* Add click on top-level to get back to main menu

* Fix issue of bounds when navigating between projects

Clear feature queries when changing projects as causing an issue when
trying to zoom to map bounds as old projects features were still there.

* Add missing file

* Use react-leaflet-cluster

* Remove unused import

* Remove unused topNavbar class

* Add link to main page when clicking in HeaderNavBar

* Use icons for point markers

* Minor changes

* Fix unit test

* Add missing files

* Improve TypeScript types/constants to replace string literals

* Add font awesome icons

* Revert "Add font awesome icons"

This reverts commit cdae716.

* Add custom markers

* Use css module

* Refactor marker work into seperate file

* Add missing file; improve sizes and comments

* Tweak css

* Add underscore to non-exported methods

* Add aria

* Add type info
  • Loading branch information
nathanfranklin authored Dec 19, 2024
1 parent 04a764f commit a0e4ab3
Show file tree
Hide file tree
Showing 21 changed files with 634 additions and 165 deletions.
6 changes: 5 additions & 1 deletion react/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const esModules = [
'react-leaflet',
'@tacc/core-components',
'uuid',
'react-leaflet-markercluster',
].join('|');

module.exports = {
Expand Down Expand Up @@ -204,7 +205,10 @@ module.exports = {
// timers: "real",

// A map from regular expressions to paths to transformers
transform: { '^.+\\.(js|jsx|mjs)?$': 'babel-jest' },
transform: {
'^.+\\.(js|jsx|mjs)?$': 'babel-jest',
'^.+\\.css$': 'jest-transform-stub',
},

// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
transformIgnorePatterns: [`/node_modules/(?!(${esModules}))`],
Expand Down
39 changes: 20 additions & 19 deletions react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.0",
"@changey/react-leaflet-markercluster": "^4.0.0-rc1",
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
Expand All @@ -53,7 +52,8 @@
"eslint-plugin-react-hooks": "^4.6.0",
"formik": "^2.4.5",
"jwt-decode": "^4.0.0",
"leaflet": "^1.9.3",
"leaflet": "^1.9.4",
"leaflet.markercluster": "^1.5.3",
"postcss-nesting": "^12.0.3",
"prettier": "^2.7.1",
"prop-types": "^15.8.1",
Expand All @@ -62,7 +62,8 @@
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-esri-leaflet": "^2.0.1",
"react-leaflet": "^4.2.0",
"react-leaflet": "^4.2.1",
"react-leaflet-markercluster": "^4.2.1",
"react-query": "^3.39.3",
"react-redux": "^8.0.2",
"react-resize-detector": "^7.1.2",
Expand Down
16 changes: 8 additions & 8 deletions react/src/AppRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import {
Navigate,
useLocation,
} from 'react-router-dom';
import * as ROUTES from './constants/routes';
import MapProject from './pages/MapProject';
import MainMenu from './pages/MainMenu';
import Logout from './pages/Logout/Logout';
import Login from './pages/Login/Login';
import Callback from './pages/Callback/Callback';
import StreetviewCallback from './pages/StreetviewCallback/StreetviewCallback';
import { RootState } from './redux/store';
import * as ROUTES from '@hazmapper/constants/routes';
import MapProject from '@hazmapper/pages/MapProject';
import MainMenu from '@hazmapper/pages/MainMenu';
import Logout from '@hazmapper/pages/Logout/Logout';
import Login from '@hazmapper/pages/Login/Login';
import Callback from '@hazmapper/pages/Callback/Callback';
import StreetviewCallback from '@hazmapper/pages/StreetviewCallback/StreetviewCallback';
import { RootState } from '@hazmapper/redux/store';
import { isTokenValid } from '@hazmapper/utils/authUtils';
import { useBasePath } from '@hazmapper/hooks/environment';

Expand Down
4 changes: 2 additions & 2 deletions react/src/components/FeatureFileTree/FeatureFileTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const FeatureFileTree: React.FC<FeatureFileTreeProps> = ({
return directoryIds;
};

// Have all direcotories be in 'expanded' (i.e. everything is expanded)
// Have all directories be in 'expanded' (i.e. everything is expanded)
const expandedDirectories = getDirectoryNodeIds(fileNodeArray);

const convertToTreeNode = (node: FeatureFileNode) => ({
Expand Down Expand Up @@ -194,4 +194,4 @@ const FeatureFileTree: React.FC<FeatureFileTreeProps> = ({
);
};

export default React.memo(FeatureFileTree);
export default FeatureFileTree;
39 changes: 3 additions & 36 deletions react/src/components/FeatureIcon/FeatureIcon.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,16 @@
import React from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconDefinition } from '@fortawesome/fontawesome-svg-core';
import {
faCameraRetro,
faVideo,
faClipboardList,
faMapMarkerAlt,
faDrawPolygon,
faCloud,
faBezierCurve,
faRoad,
faLayerGroup,
faQuestionCircle,
} from '@fortawesome/free-solid-svg-icons';

import { FeatureType, FeatureTypeNullable } from '@hazmapper/types';
import { FeatureTypeNullable } from '@hazmapper/types';
import { featureTypeToIcon } from '@hazmapper/utils/featureIconUtil';
import styles from './FeatureIcon.module.css';

const featureTypeToIcon: Record<FeatureType, IconDefinition> = {
// Asset types
image: faCameraRetro,
video: faVideo,
questionnaire: faClipboardList,
point_cloud: faCloud /* https://tacc-main.atlassian.net/browse/WG-391 */,
streetview: faRoad,

// Geometry types
Point: faMapMarkerAlt,
LineString: faBezierCurve,
Polygon: faDrawPolygon,
MultiPoint: faMapMarkerAlt,
MultiLineString: faBezierCurve,
MultiPolygon: faDrawPolygon,
GeometryCollection: faLayerGroup,

// Collection type
collection: faLayerGroup,
};

interface Props {
featureType: FeatureTypeNullable;
}

export const FeatureIcon: React.FC<Props> = ({ featureType }) => {
const icon = featureType ? featureTypeToIcon[featureType] : faQuestionCircle;
const icon = featureTypeToIcon(featureType);

return <FontAwesomeIcon className={styles.icon} icon={icon} size="sm" />;
};
80 changes: 80 additions & 0 deletions react/src/components/Map/FitBoundsHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useEffect, useCallback, useRef } from 'react';
import * as turf from '@turf/turf';
import { useMap } from 'react-leaflet';
import { FeatureCollection, Feature } from '@hazmapper/types';
import { useFeatureSelection } from '@hazmapper/hooks';
import { MAP_CONFIG } from './config';
import L from 'leaflet';

const FitBoundsHandler: React.FC<{
featureCollection: FeatureCollection;
}> = ({ featureCollection }) => {
const map = useMap();
const hasFeatures = useRef(false);
const { selectedFeatureId } = useFeatureSelection();

const getBoundsFromFeature = useCallback(
(feature: FeatureCollection | Feature) => {
const bbox = turf.bbox(feature);
return [
[bbox[1], bbox[0]] as [number, number],
[bbox[3], bbox[2]] as [number, number],
];
},
[]
);

const zoomToFeature = useCallback(
(feature: Feature) => {
if (feature.geometry.type === 'Point') {
const coordinates = feature.geometry.coordinates;
const point = L.latLng(coordinates[1], coordinates[0]);

map.setView(point, MAP_CONFIG.maxPointSelectedFeatureZoom, {
animate: false,
});
} else {
const bounds = getBoundsFromFeature(feature);
map.fitBounds(bounds, {
maxZoom: MAP_CONFIG.maxFitBoundsSelectedFeatureZoom,
padding: [50, 50],
animate: false,
});
}
},
[map, getBoundsFromFeature]
);

// Handle initial bounds when features are loaded
useEffect(() => {
if (
featureCollection.features.length &&
!selectedFeatureId &&
!hasFeatures.current
) {
const bounds = getBoundsFromFeature(featureCollection);
map.fitBounds(bounds, {
maxZoom: MAP_CONFIG.maxFitBoundsInitialZoom,
padding: [50, 50],
});
hasFeatures.current = true;
}
}, [map, featureCollection, selectedFeatureId, getBoundsFromFeature]);

// Handle selected feature bounds
useEffect(() => {
if (selectedFeatureId) {
const activeFeature = featureCollection.features.find(
(f) => f.id === selectedFeatureId
);

if (activeFeature) {
zoomToFeature(activeFeature);
}
}
}, [map, selectedFeatureId, featureCollection, zoomToFeature]);

return null;
};

export default FitBoundsHandler;
48 changes: 48 additions & 0 deletions react/src/components/Map/Map.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.markerCluster {
/* Size and shape */
min-width: 36px;
min-height: 36px;
border-radius: 50%;

/* Colors and borders */
color: #ffffff;
background: var(--global-color-accent--normal);
border: 3px solid #ffffff;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);

/* Centering container */
display: flex;
align-items: center;
justify-content: center;

/* Text styling */
font-family: var(--global-font-family--sans);
font-size: 14px;
font-weight: 500;
line-height: 1;
text-align: center;
}

/* Style for the span containing the number */
.markerCluster span {
/* Additional centering for the text itself */
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
/* Offset slightly to account for border */
margin-top: -1px;
}

.marker {
border-radius: 50%;
}

.markerContainer {
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
4 changes: 2 additions & 2 deletions react/src/components/Map/Map.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import { render } from '@testing-library/react';
import { renderInTest } from '@hazmapper/test/testUtil';
import Map from './Map';
import { tileServerLayers } from '../../__fixtures__/tileServerLayerFixture';
import { featureCollection } from '../../__fixtures__/featuresFixture';

test('renders map', () => {
const { getByText } = render(
const { getByText } = renderInTest(
<Map baseLayers={tileServerLayers} featureCollection={featureCollection} />
);
expect(getByText(/Map/)).toBeDefined();
Expand Down
Loading

0 comments on commit a0e4ab3

Please sign in to comment.