Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task/WG-5: complete leaflet map #284

Merged
merged 30 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0372ab9
Add zoom-to-feature on feature selection
nathanfranklin Nov 17, 2024
b54a847
Do some performance optimizations
nathanfranklin Nov 17, 2024
33108d1
Refactor zoom bahvior into own file
nathanfranklin Nov 19, 2024
f183a6c
Add display of geosjson features and point clouds to map
nathanfranklin Nov 19, 2024
8587705
Refactor selecting feature to be a hook
nathanfranklin Nov 19, 2024
fba63d7
Add click on top-level to get back to main menu
nathanfranklin Nov 20, 2024
4b6188c
Fix issue of bounds when navigating between projects
nathanfranklin Nov 20, 2024
80e3977
Add missing file
nathanfranklin Nov 20, 2024
afae0a9
Use react-leaflet-cluster
nathanfranklin Nov 27, 2024
c721d90
Merge branch 'main' into task/WG-5-complete-leaflet-map
nathanfranklin Dec 3, 2024
8522a19
Remove unused import
nathanfranklin Dec 4, 2024
3721f9d
Remove unused topNavbar class
nathanfranklin Dec 4, 2024
b86b310
Add link to main page when clicking in HeaderNavBar
nathanfranklin Dec 4, 2024
e0b9e7a
Merge branch 'main' into task/WG-5-complete-leaflet-map
nathanfranklin Dec 13, 2024
ddf4e07
Use icons for point markers
nathanfranklin Dec 14, 2024
5e88daf
Minor changes
nathanfranklin Dec 17, 2024
52121ba
Fix unit test
nathanfranklin Dec 17, 2024
ad44261
Merge branch 'main' into task/WG-5-complete-leaflet-map
nathanfranklin Dec 17, 2024
276e74d
Add missing files
nathanfranklin Dec 18, 2024
5688da4
Improve TypeScript types/constants to replace string literals
nathanfranklin Dec 19, 2024
cdae716
Add font awesome icons
nathanfranklin Dec 19, 2024
3c28668
Revert "Add font awesome icons"
nathanfranklin Dec 19, 2024
1125b75
Add custom markers
nathanfranklin Dec 19, 2024
d508cc7
Use css module
nathanfranklin Dec 19, 2024
bcbb5ea
Refactor marker work into seperate file
nathanfranklin Dec 19, 2024
02e758c
Add missing file; improve sizes and comments
nathanfranklin Dec 19, 2024
a7b9f2d
Tweak css
nathanfranklin Dec 19, 2024
82099d2
Add underscore to non-exported methods
nathanfranklin Dec 19, 2024
d6c4b1e
Add aria
nathanfranklin Dec 19, 2024
b0e0382
Add type info
nathanfranklin Dec 19, 2024
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
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;
26 changes: 26 additions & 0 deletions react/src/components/Map/Map.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.markerCluster {
color: #ffffff;
background: var(--global-color-accent--normal);
border: 3px solid #ffffff;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font: 14px var(--global-font-family--sans);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
min-width: 25px;
min-height: 25px;
}

.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
Loading