381 lines
8.9 KiB
JavaScript
381 lines
8.9 KiB
JavaScript
export const featureCategories = {
|
|
plants: { label: "Plants", order: 10 },
|
|
structures: { label: "Structures", order: 20 },
|
|
infrastructure: { label: "Infrastructure", order: 30 },
|
|
reference: { label: "Map Reference", order: 40 },
|
|
};
|
|
|
|
export const featureTypes = {
|
|
lotBoundary: {
|
|
label: "Lot Boundary",
|
|
categoryId: "reference",
|
|
geometryType: "Polygon",
|
|
detailFields: {},
|
|
style: {
|
|
color: "#7b684d",
|
|
weight: 2,
|
|
dashArray: "8 5",
|
|
fillColor: "#dccaab",
|
|
fillOpacity: 0.08,
|
|
},
|
|
},
|
|
fence: {
|
|
label: "Fence",
|
|
categoryId: "structures",
|
|
geometryType: "LineString",
|
|
detailFields: {
|
|
fenceType: "string",
|
|
},
|
|
style: {
|
|
color: "#705033",
|
|
weight: 4,
|
|
opacity: 0.95,
|
|
},
|
|
},
|
|
yardGrid: {
|
|
label: "Yard Grid",
|
|
categoryId: "reference",
|
|
geometryType: "LineString",
|
|
detailFields: {
|
|
axis: "string",
|
|
offsetFeet: "number",
|
|
spacingFeet: "number",
|
|
},
|
|
style: (details) => ({
|
|
color: details.offsetFeet === 0 ? "#1f4f63" : "#2f7f98",
|
|
weight: details.offsetFeet === 0 ? 3 : 2,
|
|
opacity: details.offsetFeet === 0 ? 0.75 : 0.55,
|
|
dashArray: details.offsetFeet === 0 ? undefined : "6 5",
|
|
}),
|
|
},
|
|
house: {
|
|
label: "House",
|
|
collectionLabel: "Houses",
|
|
categoryId: "structures",
|
|
geometryType: "Polygon",
|
|
detailFields: {},
|
|
style: {
|
|
color: "#8d5a35",
|
|
weight: 2,
|
|
fillColor: "#c98d58",
|
|
fillOpacity: 0.35,
|
|
},
|
|
},
|
|
garage: {
|
|
label: "Garage",
|
|
collectionLabel: "Garages",
|
|
categoryId: "structures",
|
|
geometryType: "Polygon",
|
|
detailFields: {},
|
|
style: {
|
|
color: "#60636b",
|
|
weight: 2,
|
|
fillColor: "#a4acb9",
|
|
fillOpacity: 0.45,
|
|
},
|
|
},
|
|
porch: {
|
|
label: "Porch",
|
|
categoryId: "structures",
|
|
geometryType: "Polygon",
|
|
detailFields: {},
|
|
style: {
|
|
color: "#aa7148",
|
|
weight: 2,
|
|
fillColor: "#deb588",
|
|
fillOpacity: 0.38,
|
|
},
|
|
},
|
|
deck: {
|
|
label: "Deck",
|
|
categoryId: "structures",
|
|
geometryType: "Polygon",
|
|
detailFields: {},
|
|
style: {
|
|
color: "#7f5a3c",
|
|
weight: 2,
|
|
fillColor: "#b98c66",
|
|
fillOpacity: 0.32,
|
|
},
|
|
},
|
|
walkway: {
|
|
label: "Walkway",
|
|
collectionLabel: "Walkways",
|
|
categoryId: "structures",
|
|
geometryType: "Polygon",
|
|
detailFields: {},
|
|
style: {
|
|
color: "#b8b3a6",
|
|
weight: 2,
|
|
fillColor: "#d6d1c3",
|
|
fillOpacity: 0.75,
|
|
},
|
|
},
|
|
driveway: {
|
|
label: "Driveway",
|
|
collectionLabel: "Driveways",
|
|
categoryId: "structures",
|
|
geometryType: "Polygon",
|
|
detailFields: {},
|
|
style: {
|
|
color: "#9c9890",
|
|
weight: 2,
|
|
fillColor: "#bbb7af",
|
|
fillOpacity: 0.8,
|
|
},
|
|
},
|
|
flowerBed: {
|
|
label: "Flower Bed",
|
|
collectionLabel: "Flower Beds",
|
|
categoryId: "structures",
|
|
geometryType: "Polygon",
|
|
detailFields: {
|
|
soil: "string",
|
|
mulch: "string",
|
|
sunExposure: "string",
|
|
},
|
|
style: {
|
|
color: "#7f7f31",
|
|
weight: 2,
|
|
fillColor: "#bfc97b",
|
|
fillOpacity: 0.38,
|
|
},
|
|
},
|
|
plant: {
|
|
label: "Plant",
|
|
categoryId: "plants",
|
|
geometryType: "Point",
|
|
detailFields: {
|
|
commonName: "string",
|
|
genus: "string",
|
|
species: "string",
|
|
cultivar: "string",
|
|
gardenOrgId: "string",
|
|
measurements: "plantMeasurement[]",
|
|
canopyDiameter: "number",
|
|
height: "number",
|
|
note: "string",
|
|
},
|
|
externalLinks: [
|
|
{
|
|
label: "Garden.org",
|
|
url: (details) =>
|
|
details.gardenOrgId
|
|
? `https://garden.org/plants/view/${encodeURIComponent(details.gardenOrgId)}/`
|
|
: null,
|
|
},
|
|
],
|
|
style: {
|
|
color: "#3d7140",
|
|
weight: 2,
|
|
radius: 5,
|
|
fillColor: "#78a658",
|
|
fillOpacity: 0.8,
|
|
},
|
|
},
|
|
flower: {
|
|
label: "Flower",
|
|
collectionLabel: "Flowers",
|
|
parentKind: "plant",
|
|
geometryType: "Point",
|
|
detailFields: {
|
|
commonName: "string",
|
|
genus: "string",
|
|
species: "string",
|
|
cultivar: "string",
|
|
bloomColor: "string",
|
|
bloomSeason: "string",
|
|
measurements: "plantMeasurement[]",
|
|
canopyDiameter: "number",
|
|
height: "number",
|
|
note: "string",
|
|
},
|
|
style: {
|
|
color: "#9c3f74",
|
|
weight: 2,
|
|
fillColor: "#d979aa",
|
|
fillOpacity: 0.85,
|
|
iconUrl: "./src/assets/icons/flower.svg",
|
|
iconDiameterMeters: 0.4,
|
|
},
|
|
},
|
|
daylily: {
|
|
label: "Daylily",
|
|
collectionLabel: "Daylilies",
|
|
parentKind: "flower",
|
|
geometryType: "Point",
|
|
detailFields: {
|
|
commonName: "string",
|
|
genus: "string",
|
|
cultivar: "string",
|
|
bloomColor: "string",
|
|
bloomSeason: "string",
|
|
measurements: "plantMeasurement[]",
|
|
canopyDiameter: "number",
|
|
height: "number",
|
|
note: "string",
|
|
},
|
|
style: {
|
|
color: "#9f5e1d",
|
|
weight: 2,
|
|
radius: 5,
|
|
fillColor: "#f0b44c",
|
|
fillOpacity: 0.88,
|
|
iconUrl: "./src/assets/icons/daylily.svg",
|
|
iconDiameterMeters: 2.5 / 3.280839895,
|
|
},
|
|
},
|
|
shrub: {
|
|
label: "Shrub",
|
|
collectionLabel: "Shrubs",
|
|
parentKind: "plant",
|
|
geometryType: "Point",
|
|
detailFields: {
|
|
commonName: "string",
|
|
genus: "string",
|
|
species: "string",
|
|
cultivar: "string",
|
|
measurements: "plantMeasurement[]",
|
|
canopyDiameter: "number",
|
|
height: "number",
|
|
note: "string",
|
|
},
|
|
style: (details) => ({
|
|
color: "#426a3d",
|
|
weight: 1,
|
|
radiusMeters: (details.canopyDiameter ?? 1) / 2,
|
|
fillColor: "#7e9d58",
|
|
fillOpacity: 0.62,
|
|
}),
|
|
},
|
|
tree: {
|
|
label: "Tree",
|
|
collectionLabel: "Trees",
|
|
parentKind: "plant",
|
|
geometryType: "Point",
|
|
detailFields: {
|
|
commonName: "string",
|
|
genus: "string",
|
|
species: "string",
|
|
cultivar: "string",
|
|
measurements: "plantMeasurement[]",
|
|
canopyDiameter: "number",
|
|
height: "number",
|
|
note: "string",
|
|
},
|
|
style: (details) => ({
|
|
color: "#2f6b3d",
|
|
weight: 1,
|
|
radiusMeters: (details.canopyDiameter ?? 2) / 2,
|
|
fillColor: "#5c9e64",
|
|
fillOpacity: 0.55,
|
|
}),
|
|
},
|
|
sprinkler: {
|
|
label: "Sprinkler",
|
|
categoryId: "infrastructure",
|
|
geometryType: "Point",
|
|
detailFields: {
|
|
zone: "string",
|
|
headType: "string",
|
|
flowRateGallonsPerMinute: "number",
|
|
},
|
|
style: {
|
|
color: "#2d7dd2",
|
|
radius: 5,
|
|
fillColor: "#88bdf2",
|
|
fillOpacity: 0.95,
|
|
},
|
|
},
|
|
bush: {
|
|
label: "Bush",
|
|
collectionLabel: "Bushes",
|
|
parentKind: "plant",
|
|
geometryType: "Point",
|
|
detailFields: {},
|
|
style: (details) => ({
|
|
color: "#426a3d",
|
|
weight: 1,
|
|
radiusMeters: (details.canopyDiameter ?? 1) / 2,
|
|
fillColor: "#7e9d58",
|
|
fillOpacity: 0.62,
|
|
}),
|
|
},
|
|
};
|
|
|
|
export function getFeatureType(kind) {
|
|
const definition = featureTypes[kind];
|
|
if (!definition) {
|
|
throw new Error(`Unknown feature type: ${kind}`);
|
|
}
|
|
|
|
return definition;
|
|
}
|
|
|
|
export function listFeatureTypeSchemas() {
|
|
return Object.entries(featureTypes).map(([kind, definition]) => ({
|
|
kind,
|
|
label: definition.label,
|
|
collectionLabel: definition.collectionLabel ?? definition.label,
|
|
categoryId: getFeatureCategoryId(kind),
|
|
parentKind: definition.parentKind ?? null,
|
|
geometryType: definition.geometryType,
|
|
detailFields: getInheritedDetailFields(kind),
|
|
externalLinks: getInheritedExternalLinks(kind).map((link) => ({
|
|
label: link.label,
|
|
})),
|
|
}));
|
|
}
|
|
|
|
export function listFeatureCategorySchemas() {
|
|
return Object.entries(featureCategories)
|
|
.map(([id, definition]) => ({ id, ...definition }))
|
|
.sort((left, right) => left.order - right.order);
|
|
}
|
|
|
|
export function resolveFeatureStyle(kind, details = {}) {
|
|
const { style } = getFeatureType(kind);
|
|
return typeof style === "function" ? style(details) : { ...style };
|
|
}
|
|
|
|
export function resolveFeatureLinks(kind, details = {}) {
|
|
return getInheritedExternalLinks(kind)
|
|
.map((link) => ({
|
|
label: link.label,
|
|
url: link.url(details),
|
|
}))
|
|
.filter((link) => link.url);
|
|
}
|
|
|
|
function getFeatureTypeLineage(kind) {
|
|
const lineage = [];
|
|
let currentKind = kind;
|
|
|
|
while (currentKind) {
|
|
const definition = getFeatureType(currentKind);
|
|
lineage.unshift(definition);
|
|
currentKind = definition.parentKind;
|
|
}
|
|
|
|
return lineage;
|
|
}
|
|
|
|
function getFeatureCategoryId(kind) {
|
|
const definition = [...getFeatureTypeLineage(kind)].reverse().find((entry) => entry.categoryId);
|
|
if (!definition || !featureCategories[definition.categoryId]) {
|
|
throw new Error(`Feature type has an unknown or missing category: ${kind}`);
|
|
}
|
|
return definition.categoryId;
|
|
}
|
|
|
|
function getInheritedDetailFields(kind) {
|
|
return Object.assign(
|
|
{},
|
|
...getFeatureTypeLineage(kind).map((definition) => definition.detailFields ?? {}),
|
|
);
|
|
}
|
|
|
|
function getInheritedExternalLinks(kind) {
|
|
return getFeatureTypeLineage(kind).flatMap((definition) => definition.externalLinks ?? []);
|
|
}
|