Skip to content

Commit

Permalink
feat: fetch title boundary to autofill draw boundary (#2561)
Browse files Browse the repository at this point in the history
  • Loading branch information
jessicamcinchak authored Jan 22, 2024
1 parent 3d2938e commit 066c516
Show file tree
Hide file tree
Showing 12 changed files with 36,437 additions and 1,369 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const ALLOW_LIST = [
"proposal.projectType",
"application.declaration.connection",
"property.type",
"drawBoundary.action",
];

export const getAnalyzeSessionOperations = (): Operation[] => [
Expand Down
3 changes: 2 additions & 1 deletion editor.planx.uk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@mui/material": "^5.15.2",
"@mui/styles": "^5.15.2",
"@mui/utils": "^5.15.2",
"@opensystemslab/map": "^0.7.5",
"@opensystemslab/map": "^0.7.9",
"@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#4b625d9",
"@tiptap/core": "^2.0.3",
"@tiptap/extension-bold": "^2.0.3",
Expand All @@ -36,6 +36,7 @@
"@tiptap/pm": "^2.0.3",
"@tiptap/react": "^2.0.3",
"@tiptap/suggestion": "^2.0.3",
"@turf/area": "^6.5.0",
"@turf/buffer": "^6.5.0",
"@turf/helpers": "^6.5.0",
"array-move": "^4.0.0",
Expand Down
2,642 changes: 1,380 additions & 1,262 deletions editor.planx.uk/pnpm-lock.yaml

Large diffs are not rendered by default.

116 changes: 77 additions & 39 deletions editor.planx.uk/src/@planx/components/DrawBoundary/Public/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,49 @@ import {
} from "@planx/components/shared/Preview/MapContainer";
import QuestionHeader from "@planx/components/shared/Preview/QuestionHeader";
import { PrivateFileUpload } from "@planx/components/shared/PrivateFileUpload/PrivateFileUpload";
import { squareMetresToHectares } from "@planx/components/shared/utils";
import type { PublicProps } from "@planx/components/ui";
import buffer from "@turf/buffer";
import { type GeometryObject, point } from "@turf/helpers";
import { type Feature, point } from "@turf/helpers";
import { Store, useStore } from "pages/FlowEditor/lib/store";
import React, { useEffect, useRef, useState } from "react";
import { FONT_WEIGHT_SEMI_BOLD } from "theme";
import FullWidthWrapper from "ui/public/FullWidthWrapper";

import { DrawBoundary, PASSPORT_UPLOAD_KEY } from "../model";
import {
DrawBoundary,
DrawBoundaryUserAction,
PASSPORT_COMPONENT_ACTION_KEY,
PASSPORT_UPLOAD_KEY,
} from "../model";

export type Props = PublicProps<DrawBoundary>;

export type Boundary = GeometryObject | undefined;
export type Boundary = Feature | undefined;

// Buffer applied to the address point to clip this map extent
// and applied to the site boundary and written to the passport to later clip the map extent in overview documents
const BUFFER_IN_METERS = 75;
const BUFFER_IN_METERS = 100;

export default function Component(props: Props) {
const isMounted = useRef(false);
const passport = useStore((state) => state.computePassport());

const previousBoundary =
props.previouslySubmittedData?.data?.[props.dataFieldBoundary];
props.previouslySubmittedData?.data?.[props.dataFieldBoundary] ||
passport.data?.["property.boundary.title"];
const previousArea =
props.previouslySubmittedData?.data?.[props.dataFieldArea];
props.previouslySubmittedData?.data?.[props.dataFieldArea] ||
passport.data?.["property.boundary.title.area"];
const [boundary, setBoundary] = useState<Boundary>(previousBoundary);
const [area, setArea] = useState<number | undefined>(previousArea);

const previousFile =
props.previouslySubmittedData?.data?.[PASSPORT_UPLOAD_KEY];
const startPage = previousFile ? "upload" : "draw";
const [page, setPage] = useState<"draw" | "upload">(startPage);
const passport = useStore((state) => state.computePassport());
const [boundary, setBoundary] = useState<Boundary>(previousBoundary);
const [slots, setSlots] = useState<FileUploadSlot[]>(previousFile ?? []);
const [area, setArea] = useState<number | undefined>(previousArea);

const addressPoint =
passport?.data?._address?.longitude &&
passport?.data?._address?.latitude &&
Expand Down Expand Up @@ -84,7 +95,49 @@ export default function Component(props: Props) {

return (
<Card
handleSubmit={handleSubmit}
handleSubmit={() => {
const newPassportData: Store.userData["data"] = {};

// Used the map
if (boundary && props.dataFieldBoundary) {
newPassportData[props.dataFieldBoundary] = boundary;
newPassportData[`${props.dataFieldBoundary}.buffered`] = buffer(
boundary,
BUFFER_IN_METERS,
{ units: "meters" },
);

if (area && props.dataFieldArea) {
newPassportData[props.dataFieldArea] = area;
newPassportData[`${props.dataFieldArea}.hectares`] =
squareMetresToHectares(area);
}

// Track the type of map interaction
if (
boundary?.geometry ===
passport.data?.["property.boundary.title"]?.geometry
) {
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Accept;
} else if (boundary?.properties?.dataset === "title-boundary") {
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Amend;
} else {
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Draw;
}
}

// Uploaded a file
if (slots.length) {
newPassportData[PASSPORT_UPLOAD_KEY] = slots;
newPassportData[PASSPORT_COMPONENT_ACTION_KEY] =
DrawBoundaryUserAction.Upload;
}

props.handleSubmit?.({ data: { ...newPassportData } });
}}
isValid={props.hideFileUpload ? true : Boolean(boundary || slots[0]?.url)}
>
{getBody()}
Expand All @@ -106,15 +159,20 @@ export default function Component(props: Props) {
<FullWidthWrapper>
<MapContainer environment={environment} size="large">
<p style={visuallyHidden}>
An interactive map centred on your address, with a red pointer
to draw your property boundary. Click to place points and
connect the lines to make your site. Once you've closed the site
shape, click and drag the lines to modify it.
An interactive map centred on your address, pre-populated with a
red boundary that includes the entire property, using
information from the Land Registry. You can accept this boundary
as your location plan by continuing, you can amend it by
clicking and dragging the points, or you can erase it by
clicking the reset button and draw a new custom boundary.
</p>
{!props.hideFileUpload && (
<p style={visuallyHidden}>
If you cannot draw, you can upload a location plan file using
the link below.
If you prefer to upload a location plan file instead of using
the map, please reset the map view first to erase the
pre-populated boundary. Then click the "Upload a location plan
instead" link below. A location plan can only be submitted as
a digital boundary or file, not both.
</p>
)}
{/* @ts-ignore */}
Expand All @@ -123,6 +181,7 @@ export default function Component(props: Props) {
drawMode
drawPointer="crosshair"
drawGeojsonData={JSON.stringify(boundary)}
drawGeojsonDataBuffer={10}
clipGeojsonData={
addressPoint &&
JSON.stringify(
Expand All @@ -138,6 +197,8 @@ export default function Component(props: Props) {
markerLongitude={Number(passport?.data?._address?.longitude)}
resetControlImage="trash"
osProxyEndpoint={`${process.env.REACT_APP_API_URL}/proxy/ordnance-survey`}
osCopyright={`Basemap subject to Crown copyright and database rights ${new Date().getFullYear()} OS (0)100024857`}
drawGeojsonDataCopyright={`<a href="https://www.planning.data.gov.uk/dataset/title-boundary" target="_blank">Title boundary</a> subject to Crown copyright and database rights ${new Date().getFullYear()} OS (0)100026316`}
/>
</MapContainer>
<MapFooter>
Expand Down Expand Up @@ -194,27 +255,4 @@ export default function Component(props: Props) {
);
}
}

function handleSubmit() {
const data: Store.userData["data"] = (() => {
// set userData depending if user draws boundary or uploads file
return {
[props.dataFieldBoundary]:
boundary && props.dataFieldBoundary ? boundary : undefined,
[`${props.dataFieldBoundary}.buffered`]:
boundary && props.dataFieldBoundary
? buffer(boundary, BUFFER_IN_METERS, { units: "meters" })
: undefined,
[props.dataFieldArea]:
boundary && props.dataFieldBoundary ? area : undefined,
[`${props.dataFieldArea}.hectares`]:
boundary && area && props.dataFieldBoundary
? area / 10000
: undefined,
[PASSPORT_UPLOAD_KEY]: slots.length ? slots : undefined,
};
})();

props.handleSubmit?.({ data });
}
}
8 changes: 8 additions & 0 deletions editor.planx.uk/src/@planx/components/DrawBoundary/model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { MoreInformation, parseMoreInformation } from "../shared";

export enum DrawBoundaryUserAction {
Accept = "Accepted the title boundary",
Amend = "Amended the title boundary",
Draw = "Drew a custom boundary",
Upload = "Uploaded a location plan",
}

export interface DrawBoundary extends MoreInformation {
title: string;
description: string;
Expand Down Expand Up @@ -32,3 +39,4 @@ export const DEFAULT_PASSPORT_AREA_KEY = "property.boundary.area" as const;
export const DEFAULT_TITLE = "Draw the boundary of the property" as const;
export const DEFAULT_TITLE_FOR_UPLOADING = "Upload a location plan" as const;
export const PASSPORT_UPLOAD_KEY = "proposal.drawing.locationPlan" as const; // not added to editor yet
export const PASSPORT_COMPONENT_ACTION_KEY = "drawBoundary.action" as const; // internal use only
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,41 @@ const osAddressProps = {
"property.type": ["residential.HMO.parent"],
"property.localAuthorityDistrict": ["Southwark"],
"property.region": ["London"],
"property.boundary.title.area": 1234.98,
"property.boundary.title.area.hectares": 0.123498,
"property.boundary.title": {
geometry: {
type: "MultiPolygon",
coordinates: [
[
[
[-0.076691, 51.484197],
[-0.075933, 51.484124],
[-0.075856, 51.484369],
[-0.075889, 51.484372],
[-0.0759, 51.484324],
[-0.076391, 51.484369],
[-0.076388, 51.484383],
[-0.076644, 51.484409],
[-0.076691, 51.484197],
],
],
],
},
type: "Feature",
properties: {
"entry-date": "2023-12-12",
"start-date": "2011-08-25",
"end-date": "",
entity: 12000601059,
name: "",
dataset: "title-boundary",
typology: "geography",
reference: "52725257",
prefix: "title-boundary",
"organisation-entity": "13",
},
},
};

const proposedAddressProps = {
Expand All @@ -47,6 +82,41 @@ const proposedAddressProps = {
},
"property.localAuthorityDistrict": ["Southwark"],
"property.region": ["London"],
"property.boundary.title.area": 1234.98,
"property.boundary.title.area.hectares": 0.123498,
"property.boundary.title": {
geometry: {
type: "MultiPolygon",
coordinates: [
[
[
[-0.076691, 51.484197],
[-0.075933, 51.484124],
[-0.075856, 51.484369],
[-0.075889, 51.484372],
[-0.0759, 51.484324],
[-0.076391, 51.484369],
[-0.076388, 51.484383],
[-0.076644, 51.484409],
[-0.076691, 51.484197],
],
],
],
},
type: "Feature",
properties: {
"entry-date": "2023-12-12",
"start-date": "2011-08-25",
"end-date": "",
entity: 12000601059,
name: "",
dataset: "title-boundary",
typology: "geography",
reference: "52725257",
prefix: "title-boundary",
"organisation-entity": "13",
},
},
};

jest.spyOn(SWR, "default").mockImplementation((url: any) => {
Expand Down
Loading

0 comments on commit 066c516

Please sign in to comment.