Files
a-plot-of-plants/src/lib/feature-types.js
T

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 ?? []);
}