Skip to content

Commit

Permalink
Merge pull request #226 from aodn/bugs/6008-fix-missing-centroid
Browse files Browse the repository at this point in the history
Bugs/6008 fix missing centroid
  • Loading branch information
NekoLyn authored Nov 22, 2024
2 parents 7c96d3b + f3c904a commit d37153a
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 28 deletions.
38 changes: 36 additions & 2 deletions src/components/map/mapbox/layers/Layers.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PropsWithChildren } from "react";
import { Feature, FeatureCollection, GeoJsonProperties, Point } from "geojson";
import { LngLatBounds, MapMouseEvent } from "mapbox-gl";
import { LngLatBounds, MapMouseEvent, LngLat } from "mapbox-gl";
import { AustraliaMarineParkLayer, StaticLayersDef } from "./StaticLayer";
import MapboxWorldLayer, { MapboxWorldLayersDef } from "./MapboxWorldLayer";
import * as turf from "@turf/turf";
Expand Down Expand Up @@ -30,13 +30,46 @@ const createStaticLayers = (ids: Array<string>) => (
</>
);

const normalizeLongitude = (lng: number) =>
((((lng + 180) % 360) + 360) % 360) - 180;

// Use to handle bounds across meridian
const splitLngLatBounds = (bounds: LngLatBounds): Array<LngLatBounds> => {
const sw = bounds.getSouthWest(); // Southwest corner
const ne = bounds.getNorthEast(); // Northeast corner

// We need to make it between -180 to 180 for checking
const normalNeLng = normalizeLongitude(ne.lng);
const normalSwLng = normalizeLongitude(sw.lng);
// Check if the bounds cross the anti-meridian
if (normalNeLng < normalSwLng) {
// Split into two parts: one from -180 to 180 and one from 180 to -180

const leftBounds = new LngLatBounds(
new LngLat(normalSwLng, sw.lat), // Left side (from -180 to 180)
new LngLat(180, ne.lat)
);

const rightBounds = new LngLatBounds(
new LngLat(-180, sw.lat), // Right side (from 180 to -180)
new LngLat(normalNeLng, ne.lat)
);

return [leftBounds, rightBounds];
}
// If it doesn't cross the anti-meridian, return the original bounds
return [bounds];
};

// Function to check if a point is within the map's visible bounds
const isFeatureVisible = (
feature: Feature<Point, GeoJsonProperties>,
bounds: LngLatBounds
): boolean => {
const coordinates = feature.geometry.coordinates as [number, number];
return bounds.contains(coordinates);
return splitLngLatBounds(bounds).some((polygon) =>
polygon.contains(coordinates)
);
};

// Function to determine the most "visible" point
Expand Down Expand Up @@ -117,4 +150,5 @@ export {
defaultMouseEnterEventHandler,
defaultMouseLeaveEventHandler,
findSuitableVisiblePoint,
isFeatureVisible,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, it, expect } from "vitest";
import { Feature, FeatureCollection, GeoJsonProperties, Point } from "geojson";
import { findSuitableVisiblePoint, isFeatureVisible } from "../Layers";
import * as turf from "@turf/turf";
import { LngLatBounds } from "mapbox-gl";

// Define the test where we do not need to mock the Map
describe("Test case where no map mock needed", () => {
it("should return the same feature collection if no map is provided", () => {
const featureCollection: FeatureCollection<Point> = {
type: "FeatureCollection",
features: [],
};

const result = findSuitableVisiblePoint(featureCollection, null);

// Expect the same input to be returned since the map is null
expect(result).toEqual(featureCollection);
});

it("verfiy anti-meridian works for isFeatureVisible", () => {
// The point value isn't too important as long as it is below 180 for x
const target: Feature<Point, GeoJsonProperties> = {
type: "Feature",
geometry: {
type: "Point",
coordinates: [158.72, -25.95],
},
properties: {
name: "Example Point",
description: "This is an example of a GeoJSON Point Feature",
},
};

const bounds: LngLatBounds = new LngLatBounds([
[-203.62, -43.828],
[-142.79, -8.759],
]);
// We set a LngLat where x is < -180 to create anti-meridian
expect(isFeatureVisible(target, bounds)).toBeTruthy();
});
});
56 changes: 30 additions & 26 deletions src/components/map/mapbox/layers/__test__/Layers.test.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,39 @@
import { describe, it, expect, vi } from "vitest";
import { FeatureCollection, Point } from "geojson";
import { findSuitableVisiblePoint } from "../Layers";
import { Map } from "mapbox-gl";

// Mock the MapboxGL Map class
vi.mock("mapbox-gl", () => ({
Map: vi.fn().mockImplementation(() => ({
getBounds: vi.fn().mockReturnValue({
contains: vi.fn().mockReturnValue(true),
}),
getCenter: vi.fn().mockReturnValue({
lng: 0,
lat: 0,
}),
})),
}));
import { LngLatBounds, Map } from "mapbox-gl";

// Define the test
describe("findMostVisiblePoint", () => {
beforeAll(() => {
// Mock the MapboxGL Map class
vi.mock("mapbox-gl", async () => {
const actual =
await vi.importActual<typeof import("mapbox-gl")>("mapbox-gl");

return {
...actual,
Map: vi.fn().mockImplementation(() => ({
// This is the current visible area of the map,
// we mock some random value to make test work
getBounds: vi.fn().mockReturnValue({
getSouthWest: () => [-203.62, -43.828],
getNorthEast: () => [-142.79, -8.759],
contains: vi.fn().mockReturnValue(true),
}),
getCenter: vi.fn().mockReturnValue({
lng: 0,
lat: 0,
}),
})),
};
});
});

afterEach(() => {
vi.resetAllMocks();
});

it("should return the most visible and closest points by uuid", () => {
// Define a mock feature collection
const featureCollection: FeatureCollection<Point> = {
Expand Down Expand Up @@ -92,16 +108,4 @@ describe("findMostVisiblePoint", () => {
// Expect the result to match the expected output
expect(result).toEqual(expected);
});

it("should return the same feature collection if no map is provided", () => {
const featureCollection: FeatureCollection<Point> = {
type: "FeatureCollection",
features: [],
};

const result = findSuitableVisiblePoint(featureCollection, null);

// Expect the same input to be returned since the map is null
expect(result).toEqual(featureCollection);
});
});

0 comments on commit d37153a

Please sign in to comment.