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 403 create asset geometry component #295

Merged
merged 11 commits into from
Jan 8, 2025
14 changes: 6 additions & 8 deletions react/src/components/AssetDetail/AssetDetail.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
.middleSection {
flex: 1 1 auto;
overflow: hidden;
min-height: 0;
min-height: 20%;
display: flex;
text-align: center;
flex-direction: column;
Expand All @@ -41,26 +41,25 @@
}
.bottomSection {
display: block;
flex: 0 0 auto;
flex: 0 1 auto;
overflow-x: hidden;
justify-items: flex-end;
align-items: flex-end;
width: 100%;
}
.metadataTable {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Things taken out here were styles that were not being applied by the browser

flex-grow: 1;
width: 100%;
}
.metadataTable table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
margin: 5px;
margin-top: 5px;
margin-bottom: 5px;
padding: 5px;
}
.metadataTable thead,
th {
background: #d0d0d0;
background: var(--global-color-primary--light);
text-emphasis: var(--global-color-primary--dark);
}
.metadataTable tbody {
display: block;
Expand All @@ -81,5 +80,4 @@ th {
word-wrap: break-word;
word-break: break-word;
white-space: normal;
text-overflow: clip;
}
11 changes: 9 additions & 2 deletions react/src/components/AssetDetail/AssetDetail.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import AssetDetail from './AssetDetail';
import { mockImgFeature } from '@hazmapper/__fixtures__/featuresFixture';

Expand All @@ -10,6 +10,12 @@ jest.mock('@hazmapper/hooks', () => ({
}),
}));

jest.mock('./AssetGeometry', () => {
return function AssetGeometry() {
return <div data-testid="asset-geometry">Geometry Details</div>;
};
});

describe('AssetDetail', () => {
const AssetModalProps = {
onClose: jest.fn(),
Expand All @@ -19,10 +25,11 @@ describe('AssetDetail', () => {

it('renders all main components', () => {
const { getByText } = render(<AssetDetail {...AssetModalProps} />);
const assetGeometry = screen.getByTestId('asset-geometry');
// Check for title, button, and tables
expect(getByText('Photo 4.jpg')).toBeDefined();
expect(getByText('Download')).toBeDefined();
expect(getByText('Metadata')).toBeDefined();
expect(getByText('Geometry')).toBeDefined();
expect(assetGeometry).toBeDefined();
});
});
34 changes: 5 additions & 29 deletions react/src/components/AssetDetail/AssetDetail.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Suspense } from 'react';
import _ from 'lodash';
import AssetGeometry from './AssetGeometry';
import { useAppConfiguration } from '@hazmapper/hooks';
import { FeatureTypeNullable, Feature, getFeatureType } from '@hazmapper/types';
import { FeatureIcon } from '@hazmapper/components/FeatureIcon';
Expand Down Expand Up @@ -97,7 +98,9 @@ const AssetDetail: React.FC<AssetModalProps> = ({
<table>
<thead>
<tr>
<th colSpan={2}>Metadata</th>
<th colSpan={2} className="text-center">
Metadata
</th>
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -125,34 +128,7 @@ const AssetDetail: React.FC<AssetModalProps> = ({
)}
</tbody>
</table>
<table>
<thead>
<tr>
<th colSpan={2}>Geometry</th>
</tr>
</thead>
<tbody>
{selectedFeature?.geometry &&
Object.entries(selectedFeature.geometry).map(
([propKey, propValue]) =>
propValue &&
propValue !== undefined &&
propValue.toString().trim() !== '' &&
propValue.toString() !== 'null' && (
<tr key={propKey}>
<td>{_.trim(_.startCase(propKey.toString()), '"')}</td>
<td>
{' '}
{Array.isArray(propValue) && propValue.length === 2
? `Latitude: ${propValue[0].toString()},
Longitude: ${propValue[1].toString()}`
: _.trim(JSON.stringify(propValue), '"')}
</td>
</tr>
)
)}
</tbody>
</table>
<AssetGeometry selectedFeature={selectedFeature} />
</div>
</div>
</div>
Expand Down
28 changes: 28 additions & 0 deletions react/src/components/AssetDetail/AssetGeometry.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { render } from '@testing-library/react';
import GeometryAsset from './AssetGeometry';
import { mockImgFeature } from '@hazmapper/__fixtures__/featuresFixture';

jest.mock('@turf/turf', () => ({
...jest.requireActual('@turf/turf'),
area: jest.fn(() => 1234.56),
length: jest.fn(() => 5678.9),
bbox: jest.fn(() => [10, 20, 30, 40]),
}));

jest.mock('@hazmapper/hooks', () => ({
useFeatureSelection: jest.fn(),
}));

describe('AssetGeometry', () => {
const GeometryAssetProps = {
selectedFeature: mockImgFeature,
};

it('renders geometry for point on an image', () => {
const { getByText } = render(<GeometryAsset {...GeometryAssetProps} />);
expect(getByText('Geometry: Point')).toBeDefined();
expect(getByText('Latitude')).toBeDefined();
expect(getByText('Longitude')).toBeDefined();
});
});
118 changes: 118 additions & 0 deletions react/src/components/AssetDetail/AssetGeometry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React from 'react';
import _ from 'lodash';
import * as turf from '@turf/turf';
import { Feature, FeatureType, getFeatureType } from '@hazmapper/types';

interface AssetGeometryProps {
selectedFeature: Feature;
}

const AssetGeometry: React.FC<AssetGeometryProps> = ({ selectedFeature }) => {
if (!selectedFeature?.geometry) return null;

const bbox =
selectedFeature.geometry.type !== 'Point'
? turf.bbox(selectedFeature)
: null;

const geometryType: FeatureType = getFeatureType(selectedFeature);
Copy link
Collaborator

@nathanfranklin nathanfranklin Jan 7, 2025

Choose a reason for hiding this comment

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

do you want to

A) keep the variable name and then comment that you are considering only geometry types (subset of FeatureType). 🤔

or maybe:

B) const geometryType = selectedFeature.geometry.type


there is a difference currently with angular version, as here if its an something like an image or video feature, we don't show point coordinates (as its featureType would be image and then not captures by any of the conditional rendering below. but i think would be fixed with approach (B).

(good tests by the way as covers this case well 💯 )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I think B works well. This is updated with the latest push


return (
<>
{geometryType === FeatureType.Point && (
<table>
<thead>
<tr>
<th className="text-center" colSpan={2}>
Geometry: {_.startCase(selectedFeature.geometry.type)}
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Latitude</td>
<td>{turf.bbox(selectedFeature.geometry)[0]}</td>
</tr>
<tr>
<td>Longitude</td>
<td>{turf.bbox(selectedFeature.geometry)[1]}</td>
</tr>
</tbody>
</table>
)}
{(geometryType === FeatureType.Polygon ||
geometryType === FeatureType.MultiPolygon) && (
<table>
<thead>
<tr>
<th className="text-center" colSpan={2}>
Geometry: {_.startCase(selectedFeature.geometry.type)}
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Area (m²)</td>
<td>{turf.area(selectedFeature as turf.Feature).toFixed(2)}</td>
</tr>
</tbody>
</table>
)}
{(geometryType === FeatureType.LineString ||
geometryType === FeatureType.MultiLineString) && (
<table>
<thead>
<tr>
<th className="text-center" colSpan={2}>
Geometry: {_.startCase(selectedFeature.geometry.type)}
</th>
</tr>
</thead>
<tbody>
<tr>
<td>Length (m)</td>
<td>{turf.length(selectedFeature as turf.Feature).toFixed(2)}</td>
</tr>
</tbody>
</table>
)}
{geometryType !== FeatureType.Point && bbox && (
<table>
<thead>
{selectedFeature.geometry.type === 'GeometryCollection' && (
<tr>
<th className="text-center" colSpan={2}>
Geometry: {_.startCase(selectedFeature.geometry.type)}
</th>
</tr>
)}
<tr>
<th colSpan={3} className="text-center">
Bounding Box
</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td>Latitude</td>
<td>Longitude</td>
</tr>
<tr>
<td>Minimum</td>
<td>{turf.bbox(selectedFeature.geometry)[1]}</td>
<td>{turf.bbox(selectedFeature.geometry)[0]}</td>
</tr>
<tr>
<td>Maximum</td>
<td>{turf.bbox(selectedFeature.geometry)[3]}</td>
<td>{turf.bbox(selectedFeature.geometry)[2]}</td>
</tr>
</tbody>
</table>
)}
</>
);
};

export default AssetGeometry;
Loading