Page Not Found | SeaSketch Geoprocessing
-
+
diff --git a/assets/js/339f6315.a02f4aed.js b/assets/js/339f6315.a02f4aed.js
new file mode 100644
index 000000000..4ec42a00e
--- /dev/null
+++ b/assets/js/339f6315.a02f4aed.js
@@ -0,0 +1 @@
+"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[7955],{18114:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>i,default:()=>h,frontMatter:()=>s,metadata:()=>o,toc:()=>d});var n=t(74848),r=t(28453);const s={},i="Third Party Data",o={id:"thirdpartydata/thirdpartydata",title:"Third Party Data",description:"With all datasets, it is important to check if there is an authoritative dataset that should be used. The sources below are diverse and may offer more accuracy, but they are not necessarily authoritative.",source:"@site/docs/thirdpartydata/thirdpartydata.md",sourceDirName:"thirdpartydata",slug:"/thirdpartydata/",permalink:"/geoprocessing/docs/next/thirdpartydata/",draft:!1,unlisted:!1,editUrl:"https://github.com/seasketch/geoprocessing/tree/main/website/templates/shared/docs/thirdpartydata/thirdpartydata.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Upgrade Project",permalink:"/geoprocessing/docs/next/tutorials/upgrade"},next:{title:"Cloud Drive Syncing",permalink:"/geoprocessing/docs/next/tutorials/clouddrive"}},l={},d=[{value:"Global datasources",id:"global-datasources",level:2},{value:"Marine Regions",id:"marine-regions",level:2},{value:"OSM Land",id:"osm-land",level:2},{value:"Allen Coral Atlas",id:"allen-coral-atlas",level:2}];function c(e){const a={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",img:"img",li:"li",p:"p",ul:"ul",...(0,r.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(a.header,{children:(0,n.jsx)(a.h1,{id:"third-party-data",children:"Third Party Data"})}),"\n",(0,n.jsx)(a.p,{children:"With all datasets, it is important to check if there is an authoritative dataset that should be used. The sources below are diverse and may offer more accuracy, but they are not necessarily authoritative."}),"\n",(0,n.jsx)(a.h2,{id:"global-datasources",children:"Global datasources"}),"\n",(0,n.jsxs)(a.p,{children:["A number of global datasources are published in the ",(0,n.jsx)(a.a,{href:"https://github.com/seasketch/global-datasources",children:"global-datasources"})," project."]}),"\n",(0,n.jsxs)(a.p,{children:["These are already published as cloud-optimied GeoTIFF's and you can use their URL's directly with ",(0,n.jsx)(a.code,{children:"loadFgb"}),"."]}),"\n",(0,n.jsx)(a.h2,{id:"marine-regions",children:"Marine Regions"}),"\n",(0,n.jsxs)(a.p,{children:["The ",(0,n.jsx)(a.a,{href:"https://marineregions.org/downloads.php",children:"Marine Regions"})," website is a good starting point for boundaries relevant to marine planning including EEZ, territorial sea, etc."]}),"\n",(0,n.jsx)(a.p,{children:'It\'s not always the most accurate, but it has global coverage. If the coastline for the EEZ dataset is not accurate enough you can take the "marine and land zones" dataset and then punch out the land holes using the OSM land dataset below.'}),"\n",(0,n.jsx)(a.h2,{id:"osm-land",children:"OSM Land"}),"\n",(0,n.jsxs)(a.p,{children:["The ",(0,n.jsx)(a.a,{href:"https://osmdata.openstreetmap.de/data/land-polygons.html",children:"OSM Land"})," is a regularly updated coastline dataset. This can be a more accurate dataset than Marine Regions, particularly with coverage of very small islands that are near sea level. This dataset is the most likely to match up with map providers like Mapbox."]}),"\n",(0,n.jsx)(a.h2,{id:"allen-coral-atlas",children:"Allen Coral Atlas"}),"\n",(0,n.jsx)(a.p,{children:"Access this data as follows:"}),"\n",(0,n.jsxs)(a.ul,{children:["\n",(0,n.jsxs)(a.li,{children:["Go to ",(0,n.jsx)(a.a,{href:"https://allencoralatlas.org",children:"Allen Coral Atlas"})," and loging or register an account"]}),"\n",(0,n.jsxs)(a.li,{children:["Once logged in, go to Micronesia on the atlas page - ",(0,n.jsx)(a.a,{href:"https://allencoralatlas.org/atlas/#4.51/6.3220/153.7907",children:"https://allencoralatlas.org/atlas/#4.51/6.3220/153.7907"})]}),"\n",(0,n.jsx)(a.li,{children:"Turn on Maritime Boundaries in the layer menu on the right"}),"\n",(0,n.jsx)(a.li,{children:"Click the Micronesia EEZ on the map"}),"\n",(0,n.jsx)(a.li,{children:"Click the small Download button that appears in the map popup (icon of a page with a down arrow)"}),"\n",(0,n.jsx)(a.li,{children:"Agree to the terms and click to Prepare Download"}),"\n",(0,n.jsxs)(a.li,{children:["Extract your downloaded zip file and look for ",(0,n.jsx)(a.code,{children:"Reef-Extent/reefextent.gpkg"})," and ",(0,n.jsx)(a.code,{children:"Benthic-Map/benthic.gpkg"}),", and make the data accessible to your project through one of the data linking methods described above."]}),"\n"]}),"\n",(0,n.jsx)(a.p,{children:(0,n.jsx)(a.img,{alt:"Allen Coral Atlas Download",src:t(74241).A+"",title:"Allen Coral Atlas Download",width:"2379",height:"1177"})})]})}function h(e={}){const{wrapper:a}={...(0,r.R)(),...e.components};return a?(0,n.jsx)(a,{...e,children:(0,n.jsx)(c,{...e})}):c(e)}},74241:(e,a,t)=>{t.d(a,{A:()=>n});const n=t.p+"assets/images/AllenDownload-2a0e3233793a6271dae7b617e2eaa279.jpg"},28453:(e,a,t)=>{t.d(a,{R:()=>i,x:()=>o});var n=t(96540);const r={},s=n.createContext(r);function i(e){const a=n.useContext(s);return n.useMemo((function(){return"function"==typeof e?e(a):{...a,...e}}),[a,e])}function o(e){let a;return a=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),n.createElement(s.Provider,{value:a},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/339f6315.f4bd4e42.js b/assets/js/339f6315.f4bd4e42.js
deleted file mode 100644
index ea0f04607..000000000
--- a/assets/js/339f6315.f4bd4e42.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[7955],{18114:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>s,metadata:()=>i,toc:()=>d});var n=t(74848),r=t(28453);const s={},o="Third Party Data",i={id:"thirdpartydata/thirdpartydata",title:"Third Party Data",description:"With all datasets, it is important to check if there is an authoritative dataset that should be used. The sources below are diverse and may offer more accuracy, but they are not necessarily authoritative.",source:"@site/docs/thirdpartydata/thirdpartydata.md",sourceDirName:"thirdpartydata",slug:"/thirdpartydata/",permalink:"/geoprocessing/docs/next/thirdpartydata/",draft:!1,unlisted:!1,editUrl:"https://github.com/seasketch/geoprocessing/tree/main/website/templates/shared/docs/thirdpartydata/thirdpartydata.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Upgrade Project",permalink:"/geoprocessing/docs/next/tutorials/upgrade"},next:{title:"Cloud Drive Syncing",permalink:"/geoprocessing/docs/next/tutorials/clouddrive"}},l={},d=[{value:"Global datasources",id:"global-datasources",level:2},{value:"Marine Regions",id:"marine-regions",level:2},{value:"OSM Land",id:"osm-land",level:2},{value:"Allen Coral Atlas",id:"allen-coral-atlas",level:2}];function c(e){const a={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",img:"img",li:"li",p:"p",ul:"ul",...(0,r.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(a.header,{children:(0,n.jsx)(a.h1,{id:"third-party-data",children:"Third Party Data"})}),"\n",(0,n.jsx)(a.p,{children:"With all datasets, it is important to check if there is an authoritative dataset that should be used. The sources below are diverse and may offer more accuracy, but they are not necessarily authoritative."}),"\n",(0,n.jsx)(a.h2,{id:"global-datasources",children:"Global datasources"}),"\n",(0,n.jsxs)(a.p,{children:["A number of global datasources are published in the ",(0,n.jsx)(a.a,{href:"https://github.com/seasketch/global-datasources",children:"global-datasources"})," project."]}),"\n",(0,n.jsxs)(a.p,{children:["These are already published as cloud-optimied GeoTIFF's and flatgeobuf files and you can add them to your ",(0,n.jsx)(a.code,{children:"datasources.json"})," file."]}),"\n",(0,n.jsx)(a.h2,{id:"marine-regions",children:"Marine Regions"}),"\n",(0,n.jsxs)(a.p,{children:["The ",(0,n.jsx)(a.a,{href:"https://marineregions.org/downloads.php",children:"Marine Regions"})," website is a good starting point for boundaries relevant to marine planning including EEZ, territorial sea, etc."]}),"\n",(0,n.jsx)(a.p,{children:'It\'s not always the most accurate, but it has global coverage. If the coastline for the EEZ dataset is not accurate enough you can take the "marine and land zones" dataset and then punch out the land holes using the OSM land dataset below.'}),"\n",(0,n.jsx)(a.h2,{id:"osm-land",children:"OSM Land"}),"\n",(0,n.jsxs)(a.p,{children:["The ",(0,n.jsx)(a.a,{href:"https://osmdata.openstreetmap.de/data/land-polygons.html",children:"OSM Land"})," is a regularly updated coastline dataset. This can be a more accurate dataset than Marine Regions, particularly with coverage of very small islands that are near sea level. This dataset is the most likely to match up with map providers like Mapbox."]}),"\n",(0,n.jsx)(a.h2,{id:"allen-coral-atlas",children:"Allen Coral Atlas"}),"\n",(0,n.jsx)(a.p,{children:"Access this data as follows:"}),"\n",(0,n.jsxs)(a.ul,{children:["\n",(0,n.jsxs)(a.li,{children:["Go to ",(0,n.jsx)(a.a,{href:"https://allencoralatlas.org",children:"Allen Coral Atlas"})," and loging or register an account"]}),"\n",(0,n.jsxs)(a.li,{children:["Once logged in, go to Micronesia on the atlas page - ",(0,n.jsx)(a.a,{href:"https://allencoralatlas.org/atlas/#4.51/6.3220/153.7907",children:"https://allencoralatlas.org/atlas/#4.51/6.3220/153.7907"})]}),"\n",(0,n.jsx)(a.li,{children:"Turn on Maritime Boundaries in the layer menu on the right"}),"\n",(0,n.jsx)(a.li,{children:"Click the Micronesia EEZ on the map"}),"\n",(0,n.jsx)(a.li,{children:"Click the small Download button that appears in the map popup (icon of a page with a down arrow)"}),"\n",(0,n.jsx)(a.li,{children:"Agree to the terms and click to Prepare Download"}),"\n",(0,n.jsxs)(a.li,{children:["Extract your downloaded zip file and look for ",(0,n.jsx)(a.code,{children:"Reef-Extent/reefextent.gpkg"})," and ",(0,n.jsx)(a.code,{children:"Benthic-Map/benthic.gpkg"}),", and make the data accessible to your project through one of the data linking methods described above."]}),"\n"]}),"\n",(0,n.jsx)(a.p,{children:(0,n.jsx)(a.img,{alt:"Allen Coral Atlas Download",src:t(74241).A+"",title:"Allen Coral Atlas Download",width:"2379",height:"1177"})})]})}function h(e={}){const{wrapper:a}={...(0,r.R)(),...e.components};return a?(0,n.jsx)(a,{...e,children:(0,n.jsx)(c,{...e})}):c(e)}},74241:(e,a,t)=>{t.d(a,{A:()=>n});const n=t.p+"assets/images/AllenDownload-2a0e3233793a6271dae7b617e2eaa279.jpg"},28453:(e,a,t)=>{t.d(a,{R:()=>o,x:()=>i});var n=t(96540);const r={},s=n.createContext(r);function o(e){const a=n.useContext(s);return n.useMemo((function(){return"function"==typeof e?e(a):{...a,...e}}),[a,e])}function i(e){let a;return a=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),n.createElement(s.Provider,{value:a},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/6f9fe69a.0d9fe7c2.js b/assets/js/6f9fe69a.98e76395.js
similarity index 99%
rename from assets/js/6f9fe69a.0d9fe7c2.js
rename to assets/js/6f9fe69a.98e76395.js
index 78c5288a7..87fb28f02 100644
--- a/assets/js/6f9fe69a.0d9fe7c2.js
+++ b/assets/js/6f9fe69a.98e76395.js
@@ -1 +1 @@
-"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[52637],{51145:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>l,default:()=>o,frontMatter:()=>c,metadata:()=>r,toc:()=>t});var d=s(74848),i=s(28453);const c={},l="version",r={id:"api/geoprocessing/variables/version",title:"version",description:"Type declaration",source:"@site/docs/api/geoprocessing/variables/version.md",sourceDirName:"api/geoprocessing/variables",slug:"/api/geoprocessing/variables/version",permalink:"/geoprocessing/docs/next/api/geoprocessing/variables/version",draft:!1,unlisted:!1,editUrl:"https://github.com/seasketch/geoprocessing/tree/main/website/templates/shared/docs/api/geoprocessing/variables/version.md",tags:[],version:"current",frontMatter:{}},a={},t=[{value:"Type declaration",id:"type-declaration",level:2},{value:"author",id:"author",level:3},{value:"bin",id:"bin",level:3},{value:"bin.geoprocessing",id:"bingeoprocessing",level:3},{value:"bin.run_tests",id:"binrun_tests",level:3},{value:"bugs",id:"bugs",level:3},{value:"bugs.url",id:"bugsurl",level:3},{value:"dependencies",id:"dependencies",level:3},{value:"dependencies.@aws-sdk/client-apigatewaymanagementapi",id:"dependenciesaws-sdkclient-apigatewaymanagementapi",level:3},{value:"dependencies.@aws-sdk/client-cloudformation",id:"dependenciesaws-sdkclient-cloudformation",level:3},{value:"dependencies.@aws-sdk/client-cloudfront",id:"dependenciesaws-sdkclient-cloudfront",level:3},{value:"dependencies.@aws-sdk/client-dynamodb",id:"dependenciesaws-sdkclient-dynamodb",level:3},{value:"dependencies.@aws-sdk/client-lambda",id:"dependenciesaws-sdkclient-lambda",level:3},{value:"dependencies.@aws-sdk/client-s3",id:"dependenciesaws-sdkclient-s3",level:3},{value:"dependencies.@aws-sdk/lib-dynamodb",id:"dependenciesaws-sdklib-dynamodb",level:3},{value:"dependencies.@babel/core",id:"dependenciesbabelcore",level:3},{value:"dependencies.@babel/plugin-transform-class-properties",id:"dependenciesbabelplugin-transform-class-properties",level:3},{value:"dependencies.@babel/plugin-transform-nullish-coalescing-operator",id:"dependenciesbabelplugin-transform-nullish-coalescing-operator",level:3},{value:"dependencies.@babel/plugin-transform-numeric-separator",id:"dependenciesbabelplugin-transform-numeric-separator",level:3},{value:"dependencies.@babel/plugin-transform-optional-chaining",id:"dependenciesbabelplugin-transform-optional-chaining",level:3},{value:"dependencies.@babel/preset-env",id:"dependenciesbabelpreset-env",level:3},{value:"dependencies.@babel/preset-react",id:"dependenciesbabelpreset-react",level:3},{value:"dependencies.@babel/preset-typescript",id:"dependenciesbabelpreset-typescript",level:3},{value:"dependencies.@babel/register",id:"dependenciesbabelregister",level:3},{value:"dependencies.@popperjs/core",id:"dependenciespopperjscore",level:3},{value:"dependencies.@smithy/config-resolver",id:"dependenciessmithyconfig-resolver",level:3},{value:"dependencies.@smithy/node-config-provider",id:"dependenciessmithynode-config-provider",level:3},{value:"dependencies.@storybook/addon-essentials",id:"dependenciesstorybookaddon-essentials",level:3},{value:"dependencies.@storybook/addon-interactions",id:"dependenciesstorybookaddon-interactions",level:3},{value:"dependencies.@storybook/addon-links",id:"dependenciesstorybookaddon-links",level:3},{value:"dependencies.@storybook/blocks",id:"dependenciesstorybookblocks",level:3},{value:"dependencies.@storybook/manager-api",id:"dependenciesstorybookmanager-api",level:3},{value:"dependencies.@storybook/react",id:"dependenciesstorybookreact",level:3},{value:"dependencies.@storybook/react-vite",id:"dependenciesstorybookreact-vite",level:3},{value:"dependencies.@storybook/test",id:"dependenciesstorybooktest",level:3},{value:"dependencies.@storybook/theming",id:"dependenciesstorybooktheming",level:3},{value:"dependencies.@styled-icons/bootstrap",id:"dependenciesstyled-iconsbootstrap",level:3},{value:"dependencies.@testing-library/react",id:"dependenciestesting-libraryreact",level:3},{value:"dependencies.@turf/turf",id:"dependenciesturfturf",level:3},{value:"dependencies.@types/aws-lambda",id:"dependenciestypesaws-lambda",level:3},{value:"dependencies.@types/bytes",id:"dependenciestypesbytes",level:3},{value:"dependencies.@types/cli-progress",id:"dependenciestypescli-progress",level:3},{value:"dependencies.@types/cli-table",id:"dependenciestypescli-table",level:3},{value:"dependencies.@types/flatbush",id:"dependenciestypesflatbush",level:3},{value:"dependencies.@types/fs-extra",id:"dependenciestypesfs-extra",level:3},{value:"dependencies.@types/geobuf",id:"dependenciestypesgeobuf",level:3},{value:"dependencies.@types/geojson",id:"dependenciestypesgeojson",level:3},{value:"dependencies.@types/humanize-duration",id:"dependenciestypeshumanize-duration",level:3},{value:"dependencies.@types/inquirer",id:"dependenciestypesinquirer",level:3},{value:"dependencies.@types/json2csv",id:"dependenciestypesjson2csv",level:3},{value:"dependencies.@types/lodash",id:"dependenciestypeslodash",level:3},{value:"dependencies.@types/mock-require",id:"dependenciestypesmock-require",level:3},{value:"dependencies.@types/node",id:"dependenciestypesnode",level:3},{value:"dependencies.@types/pbf",id:"dependenciestypespbf",level:3},{value:"dependencies.@types/rbush",id:"dependenciestypesrbush",level:3},{value:"dependencies.@types/react",id:"dependenciestypesreact",level:3},{value:"dependencies.@types/react-dom",id:"dependenciestypesreact-dom",level:3},{value:"dependencies.@types/react-table",id:"dependenciestypesreact-table",level:3},{value:"dependencies.@types/uuid",id:"dependenciestypesuuid",level:3},{value:"dependencies.@vitejs/plugin-react",id:"dependenciesvitejsplugin-react",level:3},{value:"dependencies.abortcontroller-polyfill",id:"dependenciesabortcontroller-polyfill",level:3},{value:"dependencies.aws-cdk-lib",id:"dependenciesaws-cdk-lib",level:3},{value:"dependencies.aws-regions",id:"dependenciesaws-regions",level:3},{value:"dependencies.bbox-fns",id:"dependenciesbbox-fns",level:3},{value:"dependencies.bytes",id:"dependenciesbytes",level:3},{value:"dependencies.canonicalize",id:"dependenciescanonicalize",level:3},{value:"dependencies.cd",id:"dependenciescd",level:3},{value:"dependencies.chalk",id:"dependencieschalk",level:3},{value:"dependencies.classnames",id:"dependenciesclassnames",level:3},{value:"dependencies.cli-progress",id:"dependenciescli-progress",level:3},{value:"dependencies.cli-table",id:"dependenciescli-table",level:3},{value:"dependencies.commander",id:"dependenciescommander",level:3},{value:"dependencies.constructs",id:"dependenciesconstructs",level:3},{value:"dependencies.copy-node-modules",id:"dependenciescopy-node-modules",level:3},{value:"dependencies.default-import",id:"dependenciesdefault-import",level:3},{value:"dependencies.encoding",id:"dependenciesencoding",level:3},{value:"dependencies.esbuild",id:"dependenciesesbuild",level:3},{value:"dependencies.esbuild-plugin-inline-image",id:"dependenciesesbuild-plugin-inline-image",level:3},{value:"dependencies.esbuild-plugins-node-modules-polyfill",id:"dependenciesesbuild-plugins-node-modules-polyfill",level:3},{value:"dependencies.fast-deep-equal",id:"dependenciesfast-deep-equal",level:3},{value:"dependencies.finalhandler",id:"dependenciesfinalhandler",level:3},{value:"dependencies.flatbush",id:"dependenciesflatbush",level:3},{value:"dependencies.flatgeobuf",id:"dependenciesflatgeobuf",level:3},{value:"dependencies.fs-extra",id:"dependenciesfs-extra",level:3},{value:"dependencies.fuzzy-tools",id:"dependenciesfuzzy-tools",level:3},{value:"dependencies.geoblaze",id:"dependenciesgeoblaze",level:3},{value:"dependencies.geobuf",id:"dependenciesgeobuf",level:3},{value:"dependencies.geojson",id:"dependenciesgeojson",level:3},{value:"dependencies.geojson-antimeridian-cut",id:"dependenciesgeojson-antimeridian-cut",level:3},{value:"dependencies.georaster",id:"dependenciesgeoraster",level:3},{value:"dependencies.globby",id:"dependenciesglobby",level:3},{value:"dependencies.http-server",id:"dependencieshttp-server",level:3},{value:"dependencies.humanize-duration",id:"dependencieshumanize-duration",level:3},{value:"dependencies.i18next",id:"dependenciesi18next",level:3},{value:"dependencies.inquirer",id:"dependenciesinquirer",level:3},{value:"dependencies.inquirer-autocomplete-prompt",id:"dependenciesinquirer-autocomplete-prompt",level:3},{value:"dependencies.jsdom",id:"dependenciesjsdom",level:3},{value:"dependencies.json2csv",id:"dependenciesjson2csv",level:3},{value:"dependencies.lodash",id:"dependencieslodash",level:3},{value:"dependencies.mnemonist",id:"dependenciesmnemonist",level:3},{value:"dependencies.mock-require",id:"dependenciesmock-require",level:3},{value:"dependencies.node-fetch",id:"dependenciesnode-fetch",level:3},{value:"dependencies.ora",id:"dependenciesora",level:3},{value:"dependencies.pascalcase",id:"dependenciespascalcase",level:3},{value:"dependencies.pbf",id:"dependenciespbf",level:3},{value:"dependencies.polygon-clipping",id:"dependenciespolygon-clipping",level:3},{value:"dependencies.pretty-bytes",id:"dependenciespretty-bytes",level:3},{value:"dependencies.proj4",id:"dependenciesproj4",level:3},{value:"dependencies.promptly",id:"dependenciespromptly",level:3},{value:"dependencies.rbush",id:"dependenciesrbush",level:3},{value:"dependencies.react",id:"dependenciesreact",level:3},{value:"dependencies.react-dom",id:"dependenciesreact-dom",level:3},{value:"dependencies.react-error-boundary",id:"dependenciesreact-error-boundary",level:3},{value:"dependencies.react-i18next",id:"dependenciesreact-i18next",level:3},{value:"dependencies.react-popper",id:"dependenciesreact-popper",level:3},{value:"dependencies.react-table",id:"dependenciesreact-table",level:3},{value:"dependencies.react-test-renderer",id:"dependenciesreact-test-renderer",level:3},{value:"dependencies.read-package-up",id:"dependenciesread-package-up",level:3},{value:"dependencies.reproject-geojson",id:"dependenciesreproject-geojson",level:3},{value:"dependencies.runes2",id:"dependenciesrunes2",level:3},{value:"dependencies.serve-static",id:"dependenciesserve-static",level:3},{value:"dependencies.slonik",id:"dependenciesslonik",level:3},{value:"dependencies.slonik-sql-tag-raw",id:"dependenciesslonik-sql-tag-raw",level:3},{value:"dependencies.slugify",id:"dependenciesslugify",level:3},{value:"dependencies.spark-md5",id:"dependenciesspark-md5",level:3},{value:"dependencies.spdx-license-ids",id:"dependenciesspdx-license-ids",level:3},{value:"dependencies.start-server-and-test",id:"dependenciesstart-server-and-test",level:3},{value:"dependencies.storybook",id:"dependenciesstorybook",level:3},{value:"dependencies.stream-buffers",id:"dependenciesstream-buffers",level:3},{value:"dependencies.string-length",id:"dependenciesstring-length",level:3},{value:"dependencies.styled-components",id:"dependenciesstyled-components",level:3},{value:"dependencies.threads-plugin",id:"dependenciesthreads-plugin",level:3},{value:"dependencies.tsx",id:"dependenciestsx",level:3},{value:"dependencies.type-fest",id:"dependenciestype-fest",level:3},{value:"dependencies.typescript",id:"dependenciestypescript",level:3},{value:"dependencies.union-subdivided-polygons",id:"dependenciesunion-subdivided-polygons",level:3},{value:"dependencies.user-meta",id:"dependenciesuser-meta",level:3},{value:"dependencies.uuid",id:"dependenciesuuid",level:3},{value:"dependencies.vite",id:"dependenciesvite",level:3},{value:"dependencies.vite-plugin-node-polyfills",id:"dependenciesvite-plugin-node-polyfills",level:3},{value:"dependencies.vitest",id:"dependenciesvitest",level:3},{value:"dependencies.vitest-fetch-mock",id:"dependenciesvitest-fetch-mock",level:3},{value:"dependencies.ws",id:"dependenciesws",level:3},{value:"dependencies.zod",id:"dependencieszod",level:3},{value:"dependencies.zod-error",id:"dependencieszod-error",level:3},{value:"dependencies.zx",id:"dependencieszx",level:3},{value:"description",id:"description",level:3},{value:"devDependencies",id:"devdependencies",level:3},{value:"devDependencies.@testing-library/jest-dom",id:"devdependenciestesting-libraryjest-dom",level:3},{value:"devDependencies.@types/finalhandler",id:"devdependenciestypesfinalhandler",level:3},{value:"devDependencies.dynamodb-local",id:"devdependenciesdynamodb-local",level:3},{value:"devDependencies.identity-obj-proxy",id:"devdependenciesidentity-obj-proxy",level:3},{value:"devDependencies.mock-socket",id:"devdependenciesmock-socket",level:3},{value:"devDependencies.typedoc",id:"devdependenciestypedoc",level:3},{value:"exports",id:"exports",level:3},{value:"exports..",id:"exports-1",level:3},{value:"exports...import",id:"exportsimport",level:3},{value:"exports...types",id:"exportstypes",level:3},{value:"exports../client-core",id:"exportsclient-core",level:3},{value:"exports../client-ui",id:"exportsclient-ui",level:3},{value:"exports../dataproviders",id:"exportsdataproviders",level:3},{value:"exports../package.json",id:"exportspackagejson",level:3},{value:"exports../scripts",id:"exportsscripts",level:3},{value:"exports../scripts/testing",id:"exportsscriptstesting",level:3},{value:"exports../storybook",id:"exportsstorybook",level:3},{value:"homepage",id:"homepage",level:3},{value:"keywords",id:"keywords",level:3},{value:"license",id:"license",level:3},{value:"main",id:"main",level:3},{value:"module",id:"module",level:3},{value:"name",id:"name",level:3},{value:"readme",id:"readme",level:3},{value:"repository",id:"repository",level:3},{value:"repository.type",id:"repositorytype",level:3},{value:"repository.url",id:"repositoryurl",level:3},{value:"scripts",id:"scripts",level:3},{value:"scripts._prepare",id:"scripts_prepare",level:3},{value:"scripts.build",id:"scriptsbuild",level:3},{value:"scripts.build",id:"scriptsbuild-1",level:3},{value:"scripts.build",id:"scriptsbuild-2",level:3},{value:"scripts.prepare",id:"scriptsprepare",level:3},{value:"scripts.start",id:"scriptsstart",level:3},{value:"scripts.start",id:"scriptsstart-1",level:3},{value:"scripts.storybook",id:"scriptsstorybook",level:3},{value:"scripts.test",id:"scriptstest",level:3},{value:"scripts.test",id:"scriptstest-1",level:3},{value:"scripts.test:browser",id:"scriptstestbrowser",level:3},{value:"scripts.test",id:"scriptstest-2",level:3},{value:"scripts.test:e2e",id:"scriptsteste2e",level:3},{value:"scripts.test",id:"scriptstest-3",level:3},{value:"scripts.test:node",id:"scriptstestnode",level:3},{value:"scripts.test:scripts",id:"scriptstestscripts",level:3},{value:"type",id:"type",level:3},{value:"types",id:"types",level:3},{value:"version",id:"version-1",level:3}];function p(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",pre:"pre",...(0,i.R)(),...e.components};return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(n.header,{children:(0,d.jsx)(n.h1,{id:"version",children:"version"})}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"version: object;\n"})}),"\n",(0,d.jsx)(n.h2,{id:"type-declaration",children:"Type declaration"}),"\n",(0,d.jsx)(n.h3,{id:"author",children:"author"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'author: string = "Chad Burt";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"bin",children:"bin"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"bin: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"bingeoprocessing",children:"bin.geoprocessing"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'geoprocessing: string = "dist/scripts/geoprocessing.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"binrun_tests",children:"bin.run_tests"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'run_tests: string = "dist/scripts/testing/runner.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"bugs",children:"bugs"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"bugs: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"bugsurl",children:"bugs.url"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'url: string = "https://github.com/seasketch/geoprocessing/issues";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencies",children:"dependencies"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"dependencies: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-apigatewaymanagementapi",children:"dependencies.@aws-sdk/client-apigatewaymanagementapi"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-apigatewaymanagementapi: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-cloudformation",children:"dependencies.@aws-sdk/client-cloudformation"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-cloudformation: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-cloudfront",children:"dependencies.@aws-sdk/client-cloudfront"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-cloudfront: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-dynamodb",children:"dependencies.@aws-sdk/client-dynamodb"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-dynamodb: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-lambda",children:"dependencies.@aws-sdk/client-lambda"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-lambda: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-s3",children:"dependencies.@aws-sdk/client-s3"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-s3: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdklib-dynamodb",children:"dependencies.@aws-sdk/lib-dynamodb"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/lib-dynamodb: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelcore",children:"dependencies.@babel/core"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/core: string = "^7.25.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelplugin-transform-class-properties",children:"dependencies.@babel/plugin-transform-class-properties"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/plugin-transform-class-properties: string = "^7.25.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelplugin-transform-nullish-coalescing-operator",children:"dependencies.@babel/plugin-transform-nullish-coalescing-operator"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/plugin-transform-nullish-coalescing-operator: string = "7.24.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelplugin-transform-numeric-separator",children:"dependencies.@babel/plugin-transform-numeric-separator"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/plugin-transform-numeric-separator: string = "^7.24.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelplugin-transform-optional-chaining",children:"dependencies.@babel/plugin-transform-optional-chaining"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/plugin-transform-optional-chaining: string = "7.24.8";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelpreset-env",children:"dependencies.@babel/preset-env"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/preset-env: string = "^7.25.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelpreset-react",children:"dependencies.@babel/preset-react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/preset-react: string = "^7.24.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelpreset-typescript",children:"dependencies.@babel/preset-typescript"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/preset-typescript: string = "^7.24.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelregister",children:"dependencies.@babel/register"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/register: string = "^7.24.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespopperjscore",children:"dependencies.@popperjs/core"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@popperjs/core: string = "^2.11.8";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciessmithyconfig-resolver",children:"dependencies.@smithy/config-resolver"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@smithy/config-resolver: string = "^3.0.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciessmithynode-config-provider",children:"dependencies.@smithy/node-config-provider"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@smithy/node-config-provider: string = "^3.1.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookaddon-essentials",children:"dependencies.@storybook/addon-essentials"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/addon-essentials: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookaddon-interactions",children:"dependencies.@storybook/addon-interactions"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/addon-interactions: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookaddon-links",children:"dependencies.@storybook/addon-links"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/addon-links: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookblocks",children:"dependencies.@storybook/blocks"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/blocks: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookmanager-api",children:"dependencies.@storybook/manager-api"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/manager-api: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookreact",children:"dependencies.@storybook/react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/react: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookreact-vite",children:"dependencies.@storybook/react-vite"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/react-vite: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybooktest",children:"dependencies.@storybook/test"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/test: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybooktheming",children:"dependencies.@storybook/theming"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/theming: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstyled-iconsbootstrap",children:"dependencies.@styled-icons/bootstrap"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@styled-icons/bootstrap: string = "^10.47.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestesting-libraryreact",children:"dependencies.@testing-library/react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@testing-library/react: string = "^16.0.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesturfturf",children:"dependencies.@turf/turf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@turf/turf: string = "7.1.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesaws-lambda",children:"dependencies.@types/aws-lambda"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/aws-lambda: string = "^8.10.145";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesbytes",children:"dependencies.@types/bytes"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/bytes: string = "^3.1.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypescli-progress",children:"dependencies.@types/cli-progress"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/cli-progress: string = "^3.11.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypescli-table",children:"dependencies.@types/cli-table"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/cli-table: string = "^0.3.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesflatbush",children:"dependencies.@types/flatbush"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/flatbush: string = "^3.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesfs-extra",children:"dependencies.@types/fs-extra"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/fs-extra: string = "^11.0.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesgeobuf",children:"dependencies.@types/geobuf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/geobuf: string = "^3.0.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesgeojson",children:"dependencies.@types/geojson"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/geojson: string = "^7946.0.14";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypeshumanize-duration",children:"dependencies.@types/humanize-duration"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/humanize-duration: string = "^3.27.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesinquirer",children:"dependencies.@types/inquirer"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/inquirer: string = "9.0.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesjson2csv",children:"dependencies.@types/json2csv"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/json2csv: string = "^5.0.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypeslodash",children:"dependencies.@types/lodash"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/lodash: string = "^4.17.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesmock-require",children:"dependencies.@types/mock-require"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/mock-require: string = "^2.0.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesnode",children:"dependencies.@types/node"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/node: string = "^22.5.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypespbf",children:"dependencies.@types/pbf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/pbf: string = "^3.0.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesrbush",children:"dependencies.@types/rbush"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/rbush: string = "^3.0.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesreact",children:"dependencies.@types/react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/react: string = "^18.3.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesreact-dom",children:"dependencies.@types/react-dom"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/react-dom: string = "^18.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesreact-table",children:"dependencies.@types/react-table"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/react-table: string = "^7.7.20";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesuuid",children:"dependencies.@types/uuid"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/uuid: string = "^10.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesvitejsplugin-react",children:"dependencies.@vitejs/plugin-react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@vitejs/plugin-react: string = "^4.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesabortcontroller-polyfill",children:"dependencies.abortcontroller-polyfill"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'abortcontroller-polyfill: string = "^1.7.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-cdk-lib",children:"dependencies.aws-cdk-lib"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'aws-cdk-lib: string = "^2.173.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-regions",children:"dependencies.aws-regions"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'aws-regions: string = "2.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbbox-fns",children:"dependencies.bbox-fns"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'bbox-fns: string = "^0.20.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbytes",children:"dependencies.bytes"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'bytes: string = "^3.1.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescanonicalize",children:"dependencies.canonicalize"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'canonicalize: string = "^2.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescd",children:"dependencies.cd"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'cd: string = "^0.3.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieschalk",children:"dependencies.chalk"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'chalk: string = "^5.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesclassnames",children:"dependencies.classnames"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'classnames: string = "^2.5.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescli-progress",children:"dependencies.cli-progress"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'cli-progress: string = "^3.12.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescli-table",children:"dependencies.cli-table"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'cli-table: string = "^0.3.11";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescommander",children:"dependencies.commander"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'commander: string = "^12.1.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesconstructs",children:"dependencies.constructs"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'constructs: string = "^10.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescopy-node-modules",children:"dependencies.copy-node-modules"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'copy-node-modules: string = "^1.1.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesdefault-import",children:"dependencies.default-import"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'default-import: string = "^2.0.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesencoding",children:"dependencies.encoding"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'encoding: string = "^0.1.13";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesesbuild",children:"dependencies.esbuild"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'esbuild: string = "0.23.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesesbuild-plugin-inline-image",children:"dependencies.esbuild-plugin-inline-image"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'esbuild-plugin-inline-image: string = "^0.0.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesesbuild-plugins-node-modules-polyfill",children:"dependencies.esbuild-plugins-node-modules-polyfill"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'esbuild-plugins-node-modules-polyfill: string = "^1.6.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesfast-deep-equal",children:"dependencies.fast-deep-equal"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'fast-deep-equal: string = "^3.1.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesfinalhandler",children:"dependencies.finalhandler"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'finalhandler: string = "^1.2.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesflatbush",children:"dependencies.flatbush"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'flatbush: string = "^3.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesflatgeobuf",children:"dependencies.flatgeobuf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'flatgeobuf: string = "3.36.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesfs-extra",children:"dependencies.fs-extra"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'fs-extra: string = "^11.2.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesfuzzy-tools",children:"dependencies.fuzzy-tools"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'fuzzy-tools: string = "^1.2.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesgeoblaze",children:"dependencies.geoblaze"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'geoblaze: string = "2.8.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesgeobuf",children:"dependencies.geobuf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'geobuf: string = "^3.0.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesgeojson",children:"dependencies.geojson"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'geojson: string = "^0.5.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesgeojson-antimeridian-cut",children:"dependencies.geojson-antimeridian-cut"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'geojson-antimeridian-cut: string = "^0.1.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesgeoraster",children:"dependencies.georaster"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'georaster: string = "^1.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesglobby",children:"dependencies.globby"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'globby: string = "^14.0.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieshttp-server",children:"dependencies.http-server"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'http-server: string = "^14.1.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieshumanize-duration",children:"dependencies.humanize-duration"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'humanize-duration: string = "^3.32.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesi18next",children:"dependencies.i18next"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'i18next: string = "^23.14.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesinquirer",children:"dependencies.inquirer"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'inquirer: string = "9.1.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesinquirer-autocomplete-prompt",children:"dependencies.inquirer-autocomplete-prompt"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'inquirer-autocomplete-prompt: string = "^3.0.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesjsdom",children:"dependencies.jsdom"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'jsdom: string = "^25.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesjson2csv",children:"dependencies.json2csv"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'json2csv: string = "^5.0.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieslodash",children:"dependencies.lodash"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'lodash: string = "^4.17.21";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesmnemonist",children:"dependencies.mnemonist"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'mnemonist: string = "^0.39.8";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesmock-require",children:"dependencies.mock-require"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'mock-require: string = "^3.0.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesnode-fetch",children:"dependencies.node-fetch"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'node-fetch: string = "^3.3.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesora",children:"dependencies.ora"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'ora: string = "^8.1.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespascalcase",children:"dependencies.pascalcase"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'pascalcase: string = "^2.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespbf",children:"dependencies.pbf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'pbf: string = "^3.2.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespolygon-clipping",children:"dependencies.polygon-clipping"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'polygon-clipping: string = "0.15.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespretty-bytes",children:"dependencies.pretty-bytes"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'pretty-bytes: string = "^5.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesproj4",children:"dependencies.proj4"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'proj4: string = "^2.12.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespromptly",children:"dependencies.promptly"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'promptly: string = "^3.2.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesrbush",children:"dependencies.rbush"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'rbush: string = "^3.0.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact",children:"dependencies.react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react: string = "^18.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-dom",children:"dependencies.react-dom"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-dom: string = "^18.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-error-boundary",children:"dependencies.react-error-boundary"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-error-boundary: string = "^4.0.13";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-i18next",children:"dependencies.react-i18next"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-i18next: string = "^15.0.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-popper",children:"dependencies.react-popper"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-popper: string = "^2.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-table",children:"dependencies.react-table"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-table: string = "^7.8.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-test-renderer",children:"dependencies.react-test-renderer"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-test-renderer: string = "^18.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesread-package-up",children:"dependencies.read-package-up"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'read-package-up: string = "^11.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreproject-geojson",children:"dependencies.reproject-geojson"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'reproject-geojson: string = "^0.5.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesrunes2",children:"dependencies.runes2"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'runes2: string = "^1.1.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesserve-static",children:"dependencies.serve-static"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'serve-static: string = "^1.15.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesslonik",children:"dependencies.slonik"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'slonik: string = "33.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesslonik-sql-tag-raw",children:"dependencies.slonik-sql-tag-raw"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'slonik-sql-tag-raw: string = "2.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesslugify",children:"dependencies.slugify"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'slugify: string = "^1.6.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesspark-md5",children:"dependencies.spark-md5"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'spark-md5: string = "^3.0.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesspdx-license-ids",children:"dependencies.spdx-license-ids"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'spdx-license-ids: string = "^3.0.20";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstart-server-and-test",children:"dependencies.start-server-and-test"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'start-server-and-test: string = "^2.0.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybook",children:"dependencies.storybook"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'storybook: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstream-buffers",children:"dependencies.stream-buffers"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'stream-buffers: string = "^3.0.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstring-length",children:"dependencies.string-length"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'string-length: string = "^6.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstyled-components",children:"dependencies.styled-components"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'styled-components: string = "^6.1.13";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesthreads-plugin",children:"dependencies.threads-plugin"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'threads-plugin: string = "^1.4.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestsx",children:"dependencies.tsx"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'tsx: string = "^4.19.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestype-fest",children:"dependencies.type-fest"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'type-fest: string = "^4.26.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypescript",children:"dependencies.typescript"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'typescript: string = "^5.5.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesunion-subdivided-polygons",children:"dependencies.union-subdivided-polygons"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'union-subdivided-polygons: string = "^0.9.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesuser-meta",children:"dependencies.user-meta"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'user-meta: string = "^1.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesuuid",children:"dependencies.uuid"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'uuid: string = "^10.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesvite",children:"dependencies.vite"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'vite: string = "^5.4.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesvite-plugin-node-polyfills",children:"dependencies.vite-plugin-node-polyfills"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'vite-plugin-node-polyfills: string = "^0.22.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesvitest",children:"dependencies.vitest"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'vitest: string = "^2.0.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesvitest-fetch-mock",children:"dependencies.vitest-fetch-mock"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'vitest-fetch-mock: string = "^0.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesws",children:"dependencies.ws"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'ws: string = "^7.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieszod",children:"dependencies.zod"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'zod: string = "^3.23.8";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieszod-error",children:"dependencies.zod-error"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'zod-error: string = "^1.5.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieszx",children:"dependencies.zx"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'zx: string = "^8.1.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"description",children:"description"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'description: string = "Geoprocessing and reporting framework for SeaSketch 2.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependencies",children:"devDependencies"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"devDependencies: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciestesting-libraryjest-dom",children:"devDependencies.@testing-library/jest-dom"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@testing-library/jest-dom: string = "^6.5.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciestypesfinalhandler",children:"devDependencies.@types/finalhandler"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/finalhandler: string = "^1.2.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciesdynamodb-local",children:"devDependencies.dynamodb-local"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'dynamodb-local: string = "^0.0.34";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciesidentity-obj-proxy",children:"devDependencies.identity-obj-proxy"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'identity-obj-proxy: string = "^3.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciesmock-socket",children:"devDependencies.mock-socket"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'mock-socket: string = "^9.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciestypedoc",children:"devDependencies.typedoc"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'typedoc: string = "^0.26.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exports",children:"exports"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"exports: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"exports-1",children:"exports.."}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:": object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"exportsimport",children:"exports...import"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'import: string = "./dist/src/index.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportstypes",children:"exports...types"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'types: string = "./dist/src/index.d.ts";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsclient-core",children:"exports../client-core"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/client-core: string = "./dist/client-core.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsclient-ui",children:"exports../client-ui"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/client-ui: string = "./dist/client-ui.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsdataproviders",children:"exports../dataproviders"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/dataproviders: string = "./dist/dataproviders.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportspackagejson",children:"exports../package.json"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'json: string = "./package.json";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsscripts",children:"exports../scripts"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/scripts: string = "./dist/scripts/index.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsscriptstesting",children:"exports../scripts/testing"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/scripts/testing: string = "./dist/scripts/testing/index.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsstorybook",children:"exports../storybook"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/storybook: string = "./dist/src/storybook/storybook.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"homepage",children:"homepage"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'homepage: string = "https://github.com/seasketch/geoprocessing#readme";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"keywords",children:"keywords"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"keywords: string[];\n"})}),"\n",(0,d.jsx)(n.h3,{id:"license",children:"license"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'license: string = "BSD-3-Clause";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"main",children:"main"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'main: string = "dist/src/index.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"module",children:"module"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'module: string = "dist/src/index.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"name",children:"name"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'name: string = "@seasketch/geoprocessing";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"readme",children:"readme"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'readme: string = "https://github.com/seasketch/geoprocessing#readme";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"repository",children:"repository"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"repository: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"repositorytype",children:"repository.type"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'type: string = "git";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"repositoryurl",children:"repository.url"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'url: string = "git+https://github.com/seasketch/geoprocessing.git";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"scripts",children:"scripts"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"scripts: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"scripts_prepare",children:"scripts._prepare"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'_prepare: string = "npx tsx scripts/npm/prepare.ts";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"scriptsbuild",children:"scripts.build"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'build: string = "tsc";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptsbuild-1",children:["scripts.build",":storybook"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'build:storybook: string = "rm -rf ../../../gp-storybook/Next && storybook build -o ../../../gp-storybook/Next";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptsbuild-2",children:["scripts.build",":watch"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'build:watch: string = "tsc --watch";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"scriptsprepare",children:"scripts.prepare"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'prepare: string = "npm run build && npm run _prepare";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptsstart",children:["scripts.start",":data"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'start:data: string = "http-server . -c-1";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptsstart-1",children:["scripts.start",":typedoc"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"start:typedoc: string = \"npx typedoc --watch --name 'SeaSketch Geoprocessing' --includeVersion --excludeExternals --readme none\";\n"})}),"\n",(0,d.jsx)(n.h3,{id:"scriptsstorybook",children:"scripts.storybook"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'storybook: string = "storybook dev -p 6006";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"scriptstest",children:"scripts.test"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test: string = "npm run test:node && npm run test:browser";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstest-1",children:["scripts.test",":browser"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:browser: string = "vitest run -c vitest.config.browser.js";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstestbrowser",children:["scripts.test:browser",":matching"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:browser:matching: string = "vitest run -c vitest.config.browser.js -t";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstest-2",children:["scripts.test",":e2e"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"test:e2e: string = \"start-server-and-test start:data 8080 'vitest run -c vitest.config.e2e.js'\";\n"})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptsteste2e",children:["scripts.test:e2e",":matching"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:e2e:matching: string = "vitest run -c vitest.config.e2e.js -t";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstest-3",children:["scripts.test",":node"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:node: string = "vitest run -c vitest.config.node.js";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstestnode",children:["scripts.test:node",":matching"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:node:matching: string = "vitest run -c vitest.config.node.js -t";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstestscripts",children:["scripts.test:scripts",":update"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:scripts:update: string = "npm run test:scripts -- --updateSnapshot";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"type",children:"type"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'type: string = "module";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"types",children:"types"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'types: string = "dist/src/index.d.ts";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"version-1",children:"version"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'version: string = "7.0.0-beta.12";\n'})})]})}function o(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,d.jsx)(n,{...e,children:(0,d.jsx)(p,{...e})}):p(e)}},28453:(e,n,s)=>{s.d(n,{R:()=>l,x:()=>r});var d=s(96540);const i={},c=d.createContext(i);function l(e){const n=d.useContext(c);return d.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),d.createElement(c.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
+"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[52637],{51145:(e,n,s)=>{s.r(n),s.d(n,{assets:()=>a,contentTitle:()=>l,default:()=>o,frontMatter:()=>c,metadata:()=>r,toc:()=>t});var d=s(74848),i=s(28453);const c={},l="version",r={id:"api/geoprocessing/variables/version",title:"version",description:"Type declaration",source:"@site/docs/api/geoprocessing/variables/version.md",sourceDirName:"api/geoprocessing/variables",slug:"/api/geoprocessing/variables/version",permalink:"/geoprocessing/docs/next/api/geoprocessing/variables/version",draft:!1,unlisted:!1,editUrl:"https://github.com/seasketch/geoprocessing/tree/main/website/templates/shared/docs/api/geoprocessing/variables/version.md",tags:[],version:"current",frontMatter:{}},a={},t=[{value:"Type declaration",id:"type-declaration",level:2},{value:"author",id:"author",level:3},{value:"bin",id:"bin",level:3},{value:"bin.geoprocessing",id:"bingeoprocessing",level:3},{value:"bin.run_tests",id:"binrun_tests",level:3},{value:"bugs",id:"bugs",level:3},{value:"bugs.url",id:"bugsurl",level:3},{value:"dependencies",id:"dependencies",level:3},{value:"dependencies.@aws-sdk/client-apigatewaymanagementapi",id:"dependenciesaws-sdkclient-apigatewaymanagementapi",level:3},{value:"dependencies.@aws-sdk/client-cloudformation",id:"dependenciesaws-sdkclient-cloudformation",level:3},{value:"dependencies.@aws-sdk/client-cloudfront",id:"dependenciesaws-sdkclient-cloudfront",level:3},{value:"dependencies.@aws-sdk/client-dynamodb",id:"dependenciesaws-sdkclient-dynamodb",level:3},{value:"dependencies.@aws-sdk/client-lambda",id:"dependenciesaws-sdkclient-lambda",level:3},{value:"dependencies.@aws-sdk/client-s3",id:"dependenciesaws-sdkclient-s3",level:3},{value:"dependencies.@aws-sdk/lib-dynamodb",id:"dependenciesaws-sdklib-dynamodb",level:3},{value:"dependencies.@babel/core",id:"dependenciesbabelcore",level:3},{value:"dependencies.@babel/plugin-transform-class-properties",id:"dependenciesbabelplugin-transform-class-properties",level:3},{value:"dependencies.@babel/plugin-transform-nullish-coalescing-operator",id:"dependenciesbabelplugin-transform-nullish-coalescing-operator",level:3},{value:"dependencies.@babel/plugin-transform-numeric-separator",id:"dependenciesbabelplugin-transform-numeric-separator",level:3},{value:"dependencies.@babel/plugin-transform-optional-chaining",id:"dependenciesbabelplugin-transform-optional-chaining",level:3},{value:"dependencies.@babel/preset-env",id:"dependenciesbabelpreset-env",level:3},{value:"dependencies.@babel/preset-react",id:"dependenciesbabelpreset-react",level:3},{value:"dependencies.@babel/preset-typescript",id:"dependenciesbabelpreset-typescript",level:3},{value:"dependencies.@babel/register",id:"dependenciesbabelregister",level:3},{value:"dependencies.@popperjs/core",id:"dependenciespopperjscore",level:3},{value:"dependencies.@smithy/config-resolver",id:"dependenciessmithyconfig-resolver",level:3},{value:"dependencies.@smithy/node-config-provider",id:"dependenciessmithynode-config-provider",level:3},{value:"dependencies.@storybook/addon-essentials",id:"dependenciesstorybookaddon-essentials",level:3},{value:"dependencies.@storybook/addon-interactions",id:"dependenciesstorybookaddon-interactions",level:3},{value:"dependencies.@storybook/addon-links",id:"dependenciesstorybookaddon-links",level:3},{value:"dependencies.@storybook/blocks",id:"dependenciesstorybookblocks",level:3},{value:"dependencies.@storybook/manager-api",id:"dependenciesstorybookmanager-api",level:3},{value:"dependencies.@storybook/react",id:"dependenciesstorybookreact",level:3},{value:"dependencies.@storybook/react-vite",id:"dependenciesstorybookreact-vite",level:3},{value:"dependencies.@storybook/test",id:"dependenciesstorybooktest",level:3},{value:"dependencies.@storybook/theming",id:"dependenciesstorybooktheming",level:3},{value:"dependencies.@styled-icons/bootstrap",id:"dependenciesstyled-iconsbootstrap",level:3},{value:"dependencies.@testing-library/react",id:"dependenciestesting-libraryreact",level:3},{value:"dependencies.@turf/turf",id:"dependenciesturfturf",level:3},{value:"dependencies.@types/aws-lambda",id:"dependenciestypesaws-lambda",level:3},{value:"dependencies.@types/bytes",id:"dependenciestypesbytes",level:3},{value:"dependencies.@types/cli-progress",id:"dependenciestypescli-progress",level:3},{value:"dependencies.@types/cli-table",id:"dependenciestypescli-table",level:3},{value:"dependencies.@types/flatbush",id:"dependenciestypesflatbush",level:3},{value:"dependencies.@types/fs-extra",id:"dependenciestypesfs-extra",level:3},{value:"dependencies.@types/geobuf",id:"dependenciestypesgeobuf",level:3},{value:"dependencies.@types/geojson",id:"dependenciestypesgeojson",level:3},{value:"dependencies.@types/humanize-duration",id:"dependenciestypeshumanize-duration",level:3},{value:"dependencies.@types/inquirer",id:"dependenciestypesinquirer",level:3},{value:"dependencies.@types/json2csv",id:"dependenciestypesjson2csv",level:3},{value:"dependencies.@types/lodash",id:"dependenciestypeslodash",level:3},{value:"dependencies.@types/mock-require",id:"dependenciestypesmock-require",level:3},{value:"dependencies.@types/node",id:"dependenciestypesnode",level:3},{value:"dependencies.@types/pbf",id:"dependenciestypespbf",level:3},{value:"dependencies.@types/rbush",id:"dependenciestypesrbush",level:3},{value:"dependencies.@types/react",id:"dependenciestypesreact",level:3},{value:"dependencies.@types/react-dom",id:"dependenciestypesreact-dom",level:3},{value:"dependencies.@types/react-table",id:"dependenciestypesreact-table",level:3},{value:"dependencies.@types/uuid",id:"dependenciestypesuuid",level:3},{value:"dependencies.@vitejs/plugin-react",id:"dependenciesvitejsplugin-react",level:3},{value:"dependencies.abortcontroller-polyfill",id:"dependenciesabortcontroller-polyfill",level:3},{value:"dependencies.aws-cdk-lib",id:"dependenciesaws-cdk-lib",level:3},{value:"dependencies.aws-regions",id:"dependenciesaws-regions",level:3},{value:"dependencies.bbox-fns",id:"dependenciesbbox-fns",level:3},{value:"dependencies.bytes",id:"dependenciesbytes",level:3},{value:"dependencies.canonicalize",id:"dependenciescanonicalize",level:3},{value:"dependencies.cd",id:"dependenciescd",level:3},{value:"dependencies.chalk",id:"dependencieschalk",level:3},{value:"dependencies.classnames",id:"dependenciesclassnames",level:3},{value:"dependencies.cli-progress",id:"dependenciescli-progress",level:3},{value:"dependencies.cli-table",id:"dependenciescli-table",level:3},{value:"dependencies.commander",id:"dependenciescommander",level:3},{value:"dependencies.constructs",id:"dependenciesconstructs",level:3},{value:"dependencies.copy-node-modules",id:"dependenciescopy-node-modules",level:3},{value:"dependencies.default-import",id:"dependenciesdefault-import",level:3},{value:"dependencies.encoding",id:"dependenciesencoding",level:3},{value:"dependencies.esbuild",id:"dependenciesesbuild",level:3},{value:"dependencies.esbuild-plugin-inline-image",id:"dependenciesesbuild-plugin-inline-image",level:3},{value:"dependencies.esbuild-plugins-node-modules-polyfill",id:"dependenciesesbuild-plugins-node-modules-polyfill",level:3},{value:"dependencies.fast-deep-equal",id:"dependenciesfast-deep-equal",level:3},{value:"dependencies.finalhandler",id:"dependenciesfinalhandler",level:3},{value:"dependencies.flatbush",id:"dependenciesflatbush",level:3},{value:"dependencies.flatgeobuf",id:"dependenciesflatgeobuf",level:3},{value:"dependencies.fs-extra",id:"dependenciesfs-extra",level:3},{value:"dependencies.fuzzy-tools",id:"dependenciesfuzzy-tools",level:3},{value:"dependencies.geoblaze",id:"dependenciesgeoblaze",level:3},{value:"dependencies.geobuf",id:"dependenciesgeobuf",level:3},{value:"dependencies.geojson",id:"dependenciesgeojson",level:3},{value:"dependencies.geojson-antimeridian-cut",id:"dependenciesgeojson-antimeridian-cut",level:3},{value:"dependencies.georaster",id:"dependenciesgeoraster",level:3},{value:"dependencies.globby",id:"dependenciesglobby",level:3},{value:"dependencies.http-server",id:"dependencieshttp-server",level:3},{value:"dependencies.humanize-duration",id:"dependencieshumanize-duration",level:3},{value:"dependencies.i18next",id:"dependenciesi18next",level:3},{value:"dependencies.inquirer",id:"dependenciesinquirer",level:3},{value:"dependencies.inquirer-autocomplete-prompt",id:"dependenciesinquirer-autocomplete-prompt",level:3},{value:"dependencies.jsdom",id:"dependenciesjsdom",level:3},{value:"dependencies.json2csv",id:"dependenciesjson2csv",level:3},{value:"dependencies.lodash",id:"dependencieslodash",level:3},{value:"dependencies.mnemonist",id:"dependenciesmnemonist",level:3},{value:"dependencies.mock-require",id:"dependenciesmock-require",level:3},{value:"dependencies.node-fetch",id:"dependenciesnode-fetch",level:3},{value:"dependencies.ora",id:"dependenciesora",level:3},{value:"dependencies.pascalcase",id:"dependenciespascalcase",level:3},{value:"dependencies.pbf",id:"dependenciespbf",level:3},{value:"dependencies.polygon-clipping",id:"dependenciespolygon-clipping",level:3},{value:"dependencies.pretty-bytes",id:"dependenciespretty-bytes",level:3},{value:"dependencies.proj4",id:"dependenciesproj4",level:3},{value:"dependencies.promptly",id:"dependenciespromptly",level:3},{value:"dependencies.rbush",id:"dependenciesrbush",level:3},{value:"dependencies.react",id:"dependenciesreact",level:3},{value:"dependencies.react-dom",id:"dependenciesreact-dom",level:3},{value:"dependencies.react-error-boundary",id:"dependenciesreact-error-boundary",level:3},{value:"dependencies.react-i18next",id:"dependenciesreact-i18next",level:3},{value:"dependencies.react-popper",id:"dependenciesreact-popper",level:3},{value:"dependencies.react-table",id:"dependenciesreact-table",level:3},{value:"dependencies.react-test-renderer",id:"dependenciesreact-test-renderer",level:3},{value:"dependencies.read-package-up",id:"dependenciesread-package-up",level:3},{value:"dependencies.reproject-geojson",id:"dependenciesreproject-geojson",level:3},{value:"dependencies.runes2",id:"dependenciesrunes2",level:3},{value:"dependencies.serve-static",id:"dependenciesserve-static",level:3},{value:"dependencies.slonik",id:"dependenciesslonik",level:3},{value:"dependencies.slonik-sql-tag-raw",id:"dependenciesslonik-sql-tag-raw",level:3},{value:"dependencies.slugify",id:"dependenciesslugify",level:3},{value:"dependencies.spark-md5",id:"dependenciesspark-md5",level:3},{value:"dependencies.spdx-license-ids",id:"dependenciesspdx-license-ids",level:3},{value:"dependencies.start-server-and-test",id:"dependenciesstart-server-and-test",level:3},{value:"dependencies.storybook",id:"dependenciesstorybook",level:3},{value:"dependencies.stream-buffers",id:"dependenciesstream-buffers",level:3},{value:"dependencies.string-length",id:"dependenciesstring-length",level:3},{value:"dependencies.styled-components",id:"dependenciesstyled-components",level:3},{value:"dependencies.threads-plugin",id:"dependenciesthreads-plugin",level:3},{value:"dependencies.tsx",id:"dependenciestsx",level:3},{value:"dependencies.type-fest",id:"dependenciestype-fest",level:3},{value:"dependencies.typescript",id:"dependenciestypescript",level:3},{value:"dependencies.union-subdivided-polygons",id:"dependenciesunion-subdivided-polygons",level:3},{value:"dependencies.user-meta",id:"dependenciesuser-meta",level:3},{value:"dependencies.uuid",id:"dependenciesuuid",level:3},{value:"dependencies.vite",id:"dependenciesvite",level:3},{value:"dependencies.vite-plugin-node-polyfills",id:"dependenciesvite-plugin-node-polyfills",level:3},{value:"dependencies.vitest",id:"dependenciesvitest",level:3},{value:"dependencies.vitest-fetch-mock",id:"dependenciesvitest-fetch-mock",level:3},{value:"dependencies.ws",id:"dependenciesws",level:3},{value:"dependencies.zod",id:"dependencieszod",level:3},{value:"dependencies.zod-error",id:"dependencieszod-error",level:3},{value:"dependencies.zx",id:"dependencieszx",level:3},{value:"description",id:"description",level:3},{value:"devDependencies",id:"devdependencies",level:3},{value:"devDependencies.@testing-library/jest-dom",id:"devdependenciestesting-libraryjest-dom",level:3},{value:"devDependencies.@types/finalhandler",id:"devdependenciestypesfinalhandler",level:3},{value:"devDependencies.dynamodb-local",id:"devdependenciesdynamodb-local",level:3},{value:"devDependencies.identity-obj-proxy",id:"devdependenciesidentity-obj-proxy",level:3},{value:"devDependencies.mock-socket",id:"devdependenciesmock-socket",level:3},{value:"devDependencies.typedoc",id:"devdependenciestypedoc",level:3},{value:"exports",id:"exports",level:3},{value:"exports..",id:"exports-1",level:3},{value:"exports...import",id:"exportsimport",level:3},{value:"exports...types",id:"exportstypes",level:3},{value:"exports../client-core",id:"exportsclient-core",level:3},{value:"exports../client-ui",id:"exportsclient-ui",level:3},{value:"exports../dataproviders",id:"exportsdataproviders",level:3},{value:"exports../package.json",id:"exportspackagejson",level:3},{value:"exports../scripts",id:"exportsscripts",level:3},{value:"exports../scripts/testing",id:"exportsscriptstesting",level:3},{value:"exports../storybook",id:"exportsstorybook",level:3},{value:"homepage",id:"homepage",level:3},{value:"keywords",id:"keywords",level:3},{value:"license",id:"license",level:3},{value:"main",id:"main",level:3},{value:"module",id:"module",level:3},{value:"name",id:"name",level:3},{value:"readme",id:"readme",level:3},{value:"repository",id:"repository",level:3},{value:"repository.type",id:"repositorytype",level:3},{value:"repository.url",id:"repositoryurl",level:3},{value:"scripts",id:"scripts",level:3},{value:"scripts._prepare",id:"scripts_prepare",level:3},{value:"scripts.build",id:"scriptsbuild",level:3},{value:"scripts.build",id:"scriptsbuild-1",level:3},{value:"scripts.build",id:"scriptsbuild-2",level:3},{value:"scripts.prepare",id:"scriptsprepare",level:3},{value:"scripts.start",id:"scriptsstart",level:3},{value:"scripts.start",id:"scriptsstart-1",level:3},{value:"scripts.storybook",id:"scriptsstorybook",level:3},{value:"scripts.test",id:"scriptstest",level:3},{value:"scripts.test",id:"scriptstest-1",level:3},{value:"scripts.test:browser",id:"scriptstestbrowser",level:3},{value:"scripts.test",id:"scriptstest-2",level:3},{value:"scripts.test:e2e",id:"scriptsteste2e",level:3},{value:"scripts.test",id:"scriptstest-3",level:3},{value:"scripts.test:node",id:"scriptstestnode",level:3},{value:"scripts.test:scripts",id:"scriptstestscripts",level:3},{value:"type",id:"type",level:3},{value:"types",id:"types",level:3},{value:"version",id:"version-1",level:3}];function p(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",pre:"pre",...(0,i.R)(),...e.components};return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)(n.header,{children:(0,d.jsx)(n.h1,{id:"version",children:"version"})}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"version: object;\n"})}),"\n",(0,d.jsx)(n.h2,{id:"type-declaration",children:"Type declaration"}),"\n",(0,d.jsx)(n.h3,{id:"author",children:"author"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'author: string = "Chad Burt";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"bin",children:"bin"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"bin: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"bingeoprocessing",children:"bin.geoprocessing"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'geoprocessing: string = "dist/scripts/geoprocessing.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"binrun_tests",children:"bin.run_tests"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'run_tests: string = "dist/scripts/testing/runner.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"bugs",children:"bugs"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"bugs: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"bugsurl",children:"bugs.url"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'url: string = "https://github.com/seasketch/geoprocessing/issues";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencies",children:"dependencies"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"dependencies: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-apigatewaymanagementapi",children:"dependencies.@aws-sdk/client-apigatewaymanagementapi"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-apigatewaymanagementapi: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-cloudformation",children:"dependencies.@aws-sdk/client-cloudformation"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-cloudformation: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-cloudfront",children:"dependencies.@aws-sdk/client-cloudfront"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-cloudfront: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-dynamodb",children:"dependencies.@aws-sdk/client-dynamodb"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-dynamodb: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-lambda",children:"dependencies.@aws-sdk/client-lambda"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-lambda: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdkclient-s3",children:"dependencies.@aws-sdk/client-s3"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/client-s3: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-sdklib-dynamodb",children:"dependencies.@aws-sdk/lib-dynamodb"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@aws-sdk/lib-dynamodb: string = "^3.637.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelcore",children:"dependencies.@babel/core"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/core: string = "^7.25.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelplugin-transform-class-properties",children:"dependencies.@babel/plugin-transform-class-properties"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/plugin-transform-class-properties: string = "^7.25.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelplugin-transform-nullish-coalescing-operator",children:"dependencies.@babel/plugin-transform-nullish-coalescing-operator"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/plugin-transform-nullish-coalescing-operator: string = "7.24.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelplugin-transform-numeric-separator",children:"dependencies.@babel/plugin-transform-numeric-separator"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/plugin-transform-numeric-separator: string = "^7.24.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelplugin-transform-optional-chaining",children:"dependencies.@babel/plugin-transform-optional-chaining"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/plugin-transform-optional-chaining: string = "7.24.8";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelpreset-env",children:"dependencies.@babel/preset-env"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/preset-env: string = "^7.25.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelpreset-react",children:"dependencies.@babel/preset-react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/preset-react: string = "^7.24.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelpreset-typescript",children:"dependencies.@babel/preset-typescript"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/preset-typescript: string = "^7.24.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbabelregister",children:"dependencies.@babel/register"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@babel/register: string = "^7.24.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespopperjscore",children:"dependencies.@popperjs/core"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@popperjs/core: string = "^2.11.8";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciessmithyconfig-resolver",children:"dependencies.@smithy/config-resolver"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@smithy/config-resolver: string = "^3.0.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciessmithynode-config-provider",children:"dependencies.@smithy/node-config-provider"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@smithy/node-config-provider: string = "^3.1.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookaddon-essentials",children:"dependencies.@storybook/addon-essentials"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/addon-essentials: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookaddon-interactions",children:"dependencies.@storybook/addon-interactions"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/addon-interactions: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookaddon-links",children:"dependencies.@storybook/addon-links"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/addon-links: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookblocks",children:"dependencies.@storybook/blocks"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/blocks: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookmanager-api",children:"dependencies.@storybook/manager-api"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/manager-api: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookreact",children:"dependencies.@storybook/react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/react: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybookreact-vite",children:"dependencies.@storybook/react-vite"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/react-vite: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybooktest",children:"dependencies.@storybook/test"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/test: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybooktheming",children:"dependencies.@storybook/theming"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@storybook/theming: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstyled-iconsbootstrap",children:"dependencies.@styled-icons/bootstrap"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@styled-icons/bootstrap: string = "^10.47.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestesting-libraryreact",children:"dependencies.@testing-library/react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@testing-library/react: string = "^16.0.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesturfturf",children:"dependencies.@turf/turf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@turf/turf: string = "7.1.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesaws-lambda",children:"dependencies.@types/aws-lambda"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/aws-lambda: string = "^8.10.145";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesbytes",children:"dependencies.@types/bytes"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/bytes: string = "^3.1.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypescli-progress",children:"dependencies.@types/cli-progress"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/cli-progress: string = "^3.11.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypescli-table",children:"dependencies.@types/cli-table"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/cli-table: string = "^0.3.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesflatbush",children:"dependencies.@types/flatbush"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/flatbush: string = "^3.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesfs-extra",children:"dependencies.@types/fs-extra"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/fs-extra: string = "^11.0.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesgeobuf",children:"dependencies.@types/geobuf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/geobuf: string = "^3.0.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesgeojson",children:"dependencies.@types/geojson"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/geojson: string = "^7946.0.14";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypeshumanize-duration",children:"dependencies.@types/humanize-duration"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/humanize-duration: string = "^3.27.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesinquirer",children:"dependencies.@types/inquirer"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/inquirer: string = "9.0.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesjson2csv",children:"dependencies.@types/json2csv"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/json2csv: string = "^5.0.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypeslodash",children:"dependencies.@types/lodash"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/lodash: string = "^4.17.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesmock-require",children:"dependencies.@types/mock-require"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/mock-require: string = "^2.0.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesnode",children:"dependencies.@types/node"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/node: string = "^22.5.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypespbf",children:"dependencies.@types/pbf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/pbf: string = "^3.0.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesrbush",children:"dependencies.@types/rbush"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/rbush: string = "^3.0.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesreact",children:"dependencies.@types/react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/react: string = "^18.3.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesreact-dom",children:"dependencies.@types/react-dom"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/react-dom: string = "^18.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesreact-table",children:"dependencies.@types/react-table"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/react-table: string = "^7.7.20";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypesuuid",children:"dependencies.@types/uuid"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/uuid: string = "^10.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesvitejsplugin-react",children:"dependencies.@vitejs/plugin-react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@vitejs/plugin-react: string = "^4.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesabortcontroller-polyfill",children:"dependencies.abortcontroller-polyfill"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'abortcontroller-polyfill: string = "^1.7.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-cdk-lib",children:"dependencies.aws-cdk-lib"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'aws-cdk-lib: string = "^2.173.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesaws-regions",children:"dependencies.aws-regions"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'aws-regions: string = "2.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbbox-fns",children:"dependencies.bbox-fns"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'bbox-fns: string = "^0.20.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesbytes",children:"dependencies.bytes"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'bytes: string = "^3.1.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescanonicalize",children:"dependencies.canonicalize"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'canonicalize: string = "^2.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescd",children:"dependencies.cd"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'cd: string = "^0.3.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieschalk",children:"dependencies.chalk"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'chalk: string = "^5.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesclassnames",children:"dependencies.classnames"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'classnames: string = "^2.5.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescli-progress",children:"dependencies.cli-progress"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'cli-progress: string = "^3.12.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescli-table",children:"dependencies.cli-table"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'cli-table: string = "^0.3.11";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescommander",children:"dependencies.commander"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'commander: string = "^12.1.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesconstructs",children:"dependencies.constructs"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'constructs: string = "^10.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciescopy-node-modules",children:"dependencies.copy-node-modules"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'copy-node-modules: string = "^1.1.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesdefault-import",children:"dependencies.default-import"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'default-import: string = "^2.0.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesencoding",children:"dependencies.encoding"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'encoding: string = "^0.1.13";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesesbuild",children:"dependencies.esbuild"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'esbuild: string = "0.23.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesesbuild-plugin-inline-image",children:"dependencies.esbuild-plugin-inline-image"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'esbuild-plugin-inline-image: string = "^0.0.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesesbuild-plugins-node-modules-polyfill",children:"dependencies.esbuild-plugins-node-modules-polyfill"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'esbuild-plugins-node-modules-polyfill: string = "^1.6.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesfast-deep-equal",children:"dependencies.fast-deep-equal"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'fast-deep-equal: string = "^3.1.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesfinalhandler",children:"dependencies.finalhandler"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'finalhandler: string = "^1.2.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesflatbush",children:"dependencies.flatbush"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'flatbush: string = "^3.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesflatgeobuf",children:"dependencies.flatgeobuf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'flatgeobuf: string = "3.36.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesfs-extra",children:"dependencies.fs-extra"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'fs-extra: string = "^11.2.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesfuzzy-tools",children:"dependencies.fuzzy-tools"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'fuzzy-tools: string = "^1.2.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesgeoblaze",children:"dependencies.geoblaze"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'geoblaze: string = "2.8.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesgeobuf",children:"dependencies.geobuf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'geobuf: string = "^3.0.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesgeojson",children:"dependencies.geojson"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'geojson: string = "^0.5.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesgeojson-antimeridian-cut",children:"dependencies.geojson-antimeridian-cut"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'geojson-antimeridian-cut: string = "^0.1.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesgeoraster",children:"dependencies.georaster"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'georaster: string = "^1.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesglobby",children:"dependencies.globby"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'globby: string = "^14.0.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieshttp-server",children:"dependencies.http-server"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'http-server: string = "^14.1.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieshumanize-duration",children:"dependencies.humanize-duration"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'humanize-duration: string = "^3.32.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesi18next",children:"dependencies.i18next"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'i18next: string = "^23.14.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesinquirer",children:"dependencies.inquirer"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'inquirer: string = "9.1.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesinquirer-autocomplete-prompt",children:"dependencies.inquirer-autocomplete-prompt"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'inquirer-autocomplete-prompt: string = "^3.0.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesjsdom",children:"dependencies.jsdom"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'jsdom: string = "^25.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesjson2csv",children:"dependencies.json2csv"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'json2csv: string = "^5.0.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieslodash",children:"dependencies.lodash"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'lodash: string = "^4.17.21";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesmnemonist",children:"dependencies.mnemonist"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'mnemonist: string = "^0.39.8";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesmock-require",children:"dependencies.mock-require"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'mock-require: string = "^3.0.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesnode-fetch",children:"dependencies.node-fetch"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'node-fetch: string = "^3.3.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesora",children:"dependencies.ora"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'ora: string = "^8.1.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespascalcase",children:"dependencies.pascalcase"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'pascalcase: string = "^2.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespbf",children:"dependencies.pbf"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'pbf: string = "^3.2.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespolygon-clipping",children:"dependencies.polygon-clipping"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'polygon-clipping: string = "0.15.7";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespretty-bytes",children:"dependencies.pretty-bytes"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'pretty-bytes: string = "^5.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesproj4",children:"dependencies.proj4"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'proj4: string = "^2.12.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciespromptly",children:"dependencies.promptly"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'promptly: string = "^3.2.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesrbush",children:"dependencies.rbush"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'rbush: string = "^3.0.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact",children:"dependencies.react"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react: string = "^18.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-dom",children:"dependencies.react-dom"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-dom: string = "^18.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-error-boundary",children:"dependencies.react-error-boundary"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-error-boundary: string = "^4.0.13";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-i18next",children:"dependencies.react-i18next"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-i18next: string = "^15.0.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-popper",children:"dependencies.react-popper"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-popper: string = "^2.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-table",children:"dependencies.react-table"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-table: string = "^7.8.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreact-test-renderer",children:"dependencies.react-test-renderer"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'react-test-renderer: string = "^18.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesread-package-up",children:"dependencies.read-package-up"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'read-package-up: string = "^11.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesreproject-geojson",children:"dependencies.reproject-geojson"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'reproject-geojson: string = "^0.5.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesrunes2",children:"dependencies.runes2"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'runes2: string = "^1.1.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesserve-static",children:"dependencies.serve-static"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'serve-static: string = "^1.15.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesslonik",children:"dependencies.slonik"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'slonik: string = "33.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesslonik-sql-tag-raw",children:"dependencies.slonik-sql-tag-raw"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'slonik-sql-tag-raw: string = "2.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesslugify",children:"dependencies.slugify"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'slugify: string = "^1.6.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesspark-md5",children:"dependencies.spark-md5"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'spark-md5: string = "^3.0.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesspdx-license-ids",children:"dependencies.spdx-license-ids"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'spdx-license-ids: string = "^3.0.20";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstart-server-and-test",children:"dependencies.start-server-and-test"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'start-server-and-test: string = "^2.0.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstorybook",children:"dependencies.storybook"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'storybook: string = "^8.2.9";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstream-buffers",children:"dependencies.stream-buffers"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'stream-buffers: string = "^3.0.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstring-length",children:"dependencies.string-length"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'string-length: string = "^6.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesstyled-components",children:"dependencies.styled-components"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'styled-components: string = "^6.1.13";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesthreads-plugin",children:"dependencies.threads-plugin"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'threads-plugin: string = "^1.4.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestsx",children:"dependencies.tsx"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'tsx: string = "^4.19.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestype-fest",children:"dependencies.type-fest"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'type-fest: string = "^4.26.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciestypescript",children:"dependencies.typescript"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'typescript: string = "^5.5.4";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesunion-subdivided-polygons",children:"dependencies.union-subdivided-polygons"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'union-subdivided-polygons: string = "^0.9.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesuser-meta",children:"dependencies.user-meta"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'user-meta: string = "^1.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesuuid",children:"dependencies.uuid"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'uuid: string = "^10.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesvite",children:"dependencies.vite"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'vite: string = "^5.4.2";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesvite-plugin-node-polyfills",children:"dependencies.vite-plugin-node-polyfills"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'vite-plugin-node-polyfills: string = "^0.22.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesvitest",children:"dependencies.vitest"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'vitest: string = "^2.0.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesvitest-fetch-mock",children:"dependencies.vitest-fetch-mock"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'vitest-fetch-mock: string = "^0.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependenciesws",children:"dependencies.ws"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'ws: string = "^7.3.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieszod",children:"dependencies.zod"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'zod: string = "^3.23.8";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieszod-error",children:"dependencies.zod-error"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'zod-error: string = "^1.5.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"dependencieszx",children:"dependencies.zx"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'zx: string = "^8.1.5";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"description",children:"description"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'description: string = "Geoprocessing and reporting framework for SeaSketch 2.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependencies",children:"devDependencies"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"devDependencies: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciestesting-libraryjest-dom",children:"devDependencies.@testing-library/jest-dom"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@testing-library/jest-dom: string = "^6.5.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciestypesfinalhandler",children:"devDependencies.@types/finalhandler"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'@types/finalhandler: string = "^1.2.3";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciesdynamodb-local",children:"devDependencies.dynamodb-local"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'dynamodb-local: string = "^0.0.34";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciesidentity-obj-proxy",children:"devDependencies.identity-obj-proxy"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'identity-obj-proxy: string = "^3.0.0";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciesmock-socket",children:"devDependencies.mock-socket"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'mock-socket: string = "^9.3.1";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"devdependenciestypedoc",children:"devDependencies.typedoc"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'typedoc: string = "^0.26.6";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exports",children:"exports"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"exports: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"exports-1",children:"exports.."}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:": object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"exportsimport",children:"exports...import"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'import: string = "./dist/src/index.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportstypes",children:"exports...types"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'types: string = "./dist/src/index.d.ts";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsclient-core",children:"exports../client-core"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/client-core: string = "./dist/client-core.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsclient-ui",children:"exports../client-ui"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/client-ui: string = "./dist/client-ui.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsdataproviders",children:"exports../dataproviders"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/dataproviders: string = "./dist/dataproviders.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportspackagejson",children:"exports../package.json"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'json: string = "./package.json";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsscripts",children:"exports../scripts"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/scripts: string = "./dist/scripts/index.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsscriptstesting",children:"exports../scripts/testing"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/scripts/testing: string = "./dist/scripts/testing/index.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"exportsstorybook",children:"exports../storybook"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'/storybook: string = "./dist/src/storybook/storybook.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"homepage",children:"homepage"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'homepage: string = "https://github.com/seasketch/geoprocessing#readme";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"keywords",children:"keywords"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"keywords: string[];\n"})}),"\n",(0,d.jsx)(n.h3,{id:"license",children:"license"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'license: string = "BSD-3-Clause";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"main",children:"main"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'main: string = "dist/src/index.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"module",children:"module"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'module: string = "dist/src/index.js";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"name",children:"name"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'name: string = "@seasketch/geoprocessing";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"readme",children:"readme"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'readme: string = "https://github.com/seasketch/geoprocessing#readme";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"repository",children:"repository"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"repository: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"repositorytype",children:"repository.type"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'type: string = "git";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"repositoryurl",children:"repository.url"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'url: string = "git+https://github.com/seasketch/geoprocessing.git";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"scripts",children:"scripts"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"scripts: object;\n"})}),"\n",(0,d.jsx)(n.h3,{id:"scripts_prepare",children:"scripts._prepare"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'_prepare: string = "npx tsx scripts/npm/prepare.ts";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"scriptsbuild",children:"scripts.build"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'build: string = "tsc";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptsbuild-1",children:["scripts.build",":storybook"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'build:storybook: string = "rm -rf ../../../gp-storybook/Next && storybook build -o ../../../gp-storybook/Next";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptsbuild-2",children:["scripts.build",":watch"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'build:watch: string = "tsc --watch";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"scriptsprepare",children:"scripts.prepare"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'prepare: string = "npm run build && npm run _prepare";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptsstart",children:["scripts.start",":data"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'start:data: string = "http-server . -c-1";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptsstart-1",children:["scripts.start",":typedoc"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"start:typedoc: string = \"npx typedoc --watch --name 'SeaSketch Geoprocessing' --includeVersion --excludeExternals --readme none\";\n"})}),"\n",(0,d.jsx)(n.h3,{id:"scriptsstorybook",children:"scripts.storybook"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'storybook: string = "storybook dev -p 6006";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"scriptstest",children:"scripts.test"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test: string = "npm run test:node && npm run test:browser";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstest-1",children:["scripts.test",":browser"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:browser: string = "vitest run -c vitest.config.browser.js";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstestbrowser",children:["scripts.test:browser",":matching"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:browser:matching: string = "vitest run -c vitest.config.browser.js -t";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstest-2",children:["scripts.test",":e2e"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:"test:e2e: string = \"start-server-and-test start:data 8080 'vitest run -c vitest.config.e2e.js'\";\n"})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptsteste2e",children:["scripts.test:e2e",":matching"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:e2e:matching: string = "vitest run -c vitest.config.e2e.js -t";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstest-3",children:["scripts.test",":node"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:node: string = "vitest run -c vitest.config.node.js";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstestnode",children:["scripts.test:node",":matching"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:node:matching: string = "vitest run -c vitest.config.node.js -t";\n'})}),"\n",(0,d.jsxs)(n.h3,{id:"scriptstestscripts",children:["scripts.test:scripts",":update"]}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'test:scripts:update: string = "npm run test:scripts -- --updateSnapshot";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"type",children:"type"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'type: string = "module";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"types",children:"types"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'types: string = "dist/src/index.d.ts";\n'})}),"\n",(0,d.jsx)(n.h3,{id:"version-1",children:"version"}),"\n",(0,d.jsx)(n.pre,{children:(0,d.jsx)(n.code,{className:"language-ts",children:'version: string = "7.0.0-beta.13";\n'})})]})}function o(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,d.jsx)(n,{...e,children:(0,d.jsx)(p,{...e})}):p(e)}},28453:(e,n,s)=>{s.d(n,{R:()=>l,x:()=>r});var d=s(96540);const i={},c=d.createContext(i);function l(e){const n=d.useContext(c);return d.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:l(e.components),d.createElement(c.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/c5879d20.9b9f6d62.js b/assets/js/c5879d20.4d50dd76.js
similarity index 99%
rename from assets/js/c5879d20.9b9f6d62.js
rename to assets/js/c5879d20.4d50dd76.js
index e7158fc25..219db5307 100644
--- a/assets/js/c5879d20.9b9f6d62.js
+++ b/assets/js/c5879d20.4d50dd76.js
@@ -1 +1 @@
-"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[31429],{76254:(e,s,r)=>{r.r(s),r.d(s,{assets:()=>c,contentTitle:()=>i,default:()=>h,frontMatter:()=>o,metadata:()=>l,toc:()=>a});var n=r(74848),t=r(28453);const o={},i="Create New Geoprocessing Project",l={id:"tutorials/newproject",title:"Create New Geoprocessing Project",description:"This tutorial walks you through designing and creating your own geoprocessing report. It covers many of the questions and decisions you might face along the way.",source:"@site/docs/tutorials/newproject.md",sourceDirName:"tutorials",slug:"/tutorials/newproject",permalink:"/geoprocessing/docs/next/tutorials/newproject",draft:!1,unlisted:!1,editUrl:"https://github.com/seasketch/geoprocessing/tree/main/website/templates/shared/docs/tutorials/newproject.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Create Sample Project",permalink:"/geoprocessing/docs/next/tutorials/sampleproject"},next:{title:"Setup Existing Project",permalink:"/geoprocessing/docs/next/tutorials/existingproject"}},c={},a=[{value:"Where Do I Start?",id:"where-do-i-start",level:2},{value:"Design",id:"design",level:3},{value:"Start Simple",id:"start-simple",level:3},{value:"Create A SeaSketch Project",id:"create-a-seasketch-project",level:2},{value:"Initialize New Geoprocessing Project",id:"initialize-new-geoprocessing-project",level:2},{value:"Create Your First Report",id:"create-your-first-report",level:2},{value:"Build Your Project",id:"build-your-project",level:2},{value:"Deploy Project To AWS",id:"deploy-project-to-aws",level:2},{value:"Publish Datasources",id:"publish-datasources",level:2},{value:"Integrate With SeaSketch",id:"integrate-with-seasketch",level:2},{value:"Build Up Your Tests",id:"build-up-your-tests",level:2},{value:"Additional Guides",id:"additional-guides",level:2},{value:"What Next",id:"what-next",level:2}];function d(e){const s={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",ul:"ul",...(0,t.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.header,{children:(0,n.jsx)(s.h1,{id:"create-new-geoprocessing-project",children:"Create New Geoprocessing Project"})}),"\n",(0,n.jsx)(s.p,{children:"This tutorial walks you through designing and creating your own geoprocessing report. It covers many of the questions and decisions you might face along the way."}),"\n",(0,n.jsx)(s.p,{children:"This tutorial assumes:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Your ",(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/",children:"system setup"})," is complete"]}),"\n",(0,n.jsxs)(s.li,{children:["You completed the ",(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject",children:"sample project tutorial"})]}),"\n",(0,n.jsx)(s.li,{children:"Your geoprocessing virtual environment is currently running (Devcontainer or WSL)"}),"\n",(0,n.jsx)(s.li,{children:"You have VSCode open in your virtual environment with a terminal pane open"}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"where-do-i-start",children:"Where Do I Start?"}),"\n",(0,n.jsx)(s.p,{children:"Creating a geoprocessing project is not linear, it's iterative. You don't need to have all the answers for your project or understand all the features of the framework. Here's one approach:"}),"\n",(0,n.jsx)(s.h3,{id:"design",children:"Design"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Put together a rough design ",(0,n.jsx)(s.a,{href:"https://docs.google.com/document/d/1Qe7pZYmwg7ggRY9ocu3tpdTQkvuIHMr38wLxrjSitpU/edit?usp=sharing",children:"template"}),". This one asks common questions and is a good place to capture decisions."]}),"\n",(0,n.jsxs)(s.li,{children:["Explore the geoprocessing ",(0,n.jsx)(s.a,{href:"/storybook",children:"UI components library"}),"."]}),"\n",(0,n.jsx)(s.li,{children:"Look at other SeaSketch Reports and find ideas that match your needs."}),"\n"]}),"\n",(0,n.jsx)(s.h3,{id:"start-simple",children:"Start Simple"}),"\n",(0,n.jsx)(s.p,{children:"If it's not clear at this point, the geoprocessing framework is not a one-side-fits-all solution, it's a set of building blocks. Which ones you use are up to you."}),"\n",(0,n.jsx)(s.p,{children:"It takes time to figure out how it all works so keep it simple to start:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"single planning boundary or none at all"}),"\n",(0,n.jsx)(s.li,{children:"simple measurable objectives (overlap area, % area overlap, overlap feature count)"}),"\n",(0,n.jsx)(s.li,{children:"smaller datasets"}),"\n",(0,n.jsx)(s.li,{children:"no groupno classification of sketch types (e.g. protection levels)"}),"\n",(0,n.jsx)(s.li,{children:"no need to handle overlapping sketch polygons"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["A good example of this is ",(0,n.jsx)(s.a,{href:"https://github.com/underbluewaters/oregon-next",children:"Oregon"})," SeaSketch reports."]}),"\n",(0,n.jsx)(s.p,{children:"Then as your planning process gets more complex:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"multiple planning boundaries (offshore/nearshore)"}),"\n",(0,n.jsx)(s.li,{children:"multiple objectives with targets"}),"\n",(0,n.jsx)(s.li,{children:"large datasets with multiple data subclasses."}),"\n",(0,n.jsx)(s.li,{children:"long running analysis with required precalculation"}),"\n",(0,n.jsx)(s.li,{children:"use of a sketch classification system (e.g. protection levels)"}),"\n",(0,n.jsx)(s.li,{children:"need to enforce rules about overlapping sketches"}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:"Your project can benefit from more advanced features:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"Geography"})," records representing project planning boundaries"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"Metric"})," records for representing multi-dimensional analysis results."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"Objective"})," records representing objective targets per sketch class."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"MetricGroup"})," records reresenting relationship of metric results to their data classes, datasource, objective target, etc."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"toolbox"})," for calculating overlay analysis metrics at the collection level in many dimensions - by data class, by protection level, by planning boundary.","\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"overlapFeatures"}),", ",(0,n.jsx)(s.code,{children:"rasterMetrics"}),", ",(0,n.jsx)(s.code,{children:"overlapFeaturesGroupMetrics"}),", ",(0,n.jsx)(s.code,{children:"overlapRasterGroupMetrics"})]}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(s.li,{children:["UI components that can work multi-dimensional metrics","\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"ClassTable"}),", ",(0,n.jsx)(s.code,{children:"SketchClassTable"}),", ",(0,n.jsx)(s.code,{children:"GeographySwitcher"})]}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"precalc"})," command automating pre-calculation of overlay stats for combinations of Datasources and Geographies."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"worker"})," functions to run spread out geoprocessing work to run in parallel."]}),"\n",(0,n.jsxs)(s.li,{children:["Language ",(0,n.jsx)(s.code,{children:"translation"})," workflow and library of pre-translated UI components."]}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:"Examples of more complex projects:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://github.com/seasketch/california-reports",children:"California"})," - multiple geographies presented in reports (planning boundaries, bioregions), worker functions"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://github.com/seasketch/bermuda-reports-next",children:"Bermuda"})," - IUCN classification system with metrics calculated overall, per protection level, and per sketch. worker functions"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://github.com/seasketch/azores-nearshore-reports",children:"Blue Azores nearshore"})," - user switching between planning geographies."]}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"https://github.com/seasketch/samoa-reports",children:"Samoa Reports"})}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://github.com/seasketch/azores-nearshore-reports",children:"Azores Nearshore Reports"}),"."]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"create-a-seasketch-project",children:"Create A SeaSketch Project"}),"\n",(0,n.jsxs)(s.p,{children:["First things first, follow the ",(0,n.jsx)(s.a,{href:"https://docs.seasketch.org/seasketch-documentation/administrators-guide/getting-started",children:"instructions"})," to create a new SeaSketch project. This includes defining the planning bounds and ",(0,n.jsx)(s.a,{href:"https://docs.seasketch.org/seasketch-documentation/administrators-guide/sketch-classes",children:"creating a Sketch class"}),". You will want to create a ",(0,n.jsx)(s.code,{children:"Polygon"})," sketch class with a name that makes sense for you project (e.g. MPA for Marine Protected Area) and then also a ",(0,n.jsx)(s.code,{children:"Collection"})," sketch class to group instances of your polygon sketch class into. Note that sketch classes are where you will integrate your geoprocessing services to view reports, but you will not do it at this time."]}),"\n",(0,n.jsx)(s.h2,{id:"initialize-new-geoprocessing-project",children:"Initialize New Geoprocessing Project"}),"\n",(0,n.jsx)(s.p,{children:"Start with initializing a new project:"}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-sh",children:"cd /workspaces\nnpx @seasketch/geoprocessing@7.0.0-beta.12 init 7.0.0-beta.12\n"})}),"\n",(0,n.jsx)(s.p,{children:"Tips:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"the answers to all of the init questions can be changed later, so don't worry if you don't know the answer."}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://github.com/seasketch/next/blob/master/LICENSE",children:"SeaSketch"})," uses a BSD-3 license (the default choice). You can choose any including ",(0,n.jsx)(s.code,{children:"UNLICENSED"}),' meaning proprietary or "All rights reserved" .']}),"\n",(0,n.jsxs)(s.li,{children:["The most common AWS region is ",(0,n.jsx)(s.code,{children:"us-west-1"})," or ",(0,n.jsx)(s.code,{children:"us-east-2"}),". ",(0,n.jsx)(s.a,{href:"https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html",children:"Choose the region"})," closest to your project."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Learn more about your projects ",(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/structure",children:"structure"})]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject#create-git-repo",children:"Create Github repo and push"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/linkData",children:"Link data into workspace"})}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"create-your-first-report",children:"Create Your First Report"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject#reef-report",children:"Low-level vector report"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject#benthic-habitat-report",children:"High-level vector report"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject#seamount-report",children:"Low-level raster report"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject#coral-species-report",children:"High-level raster report"})}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"build-your-project",children:"Build Your Project"}),"\n",(0,n.jsxs)(s.p,{children:["The application ",(0,n.jsx)(s.code,{children:"build"})," proceess packages it for deployment. Specifically it:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Checks all the Typescript code to make sure it's valid and types are used properly."}),"\n",(0,n.jsx)(s.li,{children:"Transpiles all Typescript to Javascript"}),"\n",(0,n.jsxs)(s.li,{children:["Bundles UI report clients into the ",(0,n.jsx)(s.code,{children:".build-web"})," directory"]}),"\n",(0,n.jsxs)(s.li,{children:["Bundles geoprocessing and preprocessing functions into the ",(0,n.jsx)(s.code,{children:".build"})," directory."]}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:"To build your application run the following:"}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-bash",children:"npm run build\n"})}),"\n",(0,n.jsxs)(s.p,{children:["If the build step fails, you will need to look at the error message and figure out what you need to do. Did it fail in building the functions or the clients? 99% of the time you should be able to catch these errors sooner. If VSCode finds invalid Typescript code, it will warn you with files marked in ",(0,n.jsx)(s.code,{children:"red"})," in the Explorer panel or with red markes and squiggle text in any of the files."]}),"\n",(0,n.jsx)(s.p,{children:"If you're still not sure try some of the following:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Run your smoke tests, see if they pass"}),"\n",(0,n.jsx)(s.li,{children:"When was the last time your build did succeed? You can be sure the error is caused by a change you made since then either in your project code, by upgrading your geoprocessing library version and not migratin fully, or by changing something on your system."}),"\n",(0,n.jsx)(s.li,{children:"You can stash your current changes or commit them to a branch so they are not lost. Then sequentially check out previous commits of the code until you find one that builds properly. Now you know that the next commit cause the build error."}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"deploy-project-to-aws",children:"Deploy Project To AWS"}),"\n",(0,n.jsx)(s.p,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/deploy",children:"Deploy your project"})}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Setup AWSCLI"}),"\n",(0,n.jsx)(s.li,{children:"Deploy to AWS"}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"publish-datasources",children:"Publish Datasources"}),"\n",(0,n.jsxs)(s.p,{children:["Once you have deployed your project to AWS, it will have an S3 bucket just for publishing ",(0,n.jsx)(s.code,{children:"datasources"}),". The name of this bucket is based on the name of your project. If your project is named ",(0,n.jsx)(s.code,{children:"my-project"})," (the name assigned in your package.json file), then the bucket name will be:"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{children:"s3://gp-my-project-datasets\n"})}),"\n",(0,n.jsx)(s.p,{children:"To pubish your data run the following command:"}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-bash",children:"npm run publish:data\n"})}),"\n",(0,n.jsx)(s.p,{children:"It will ask you if you want to publish all datasources, or choose from a list."}),"\n",(0,n.jsxs)(s.p,{children:["Your datasources will need to have already been imported using ",(0,n.jsx)(s.code,{children:"import:data"})," and exist in the ",(0,n.jsx)(s.code,{children:"data/dist"})," for this to work."]}),"\n",(0,n.jsx)(s.p,{children:"Note if you don't publish your datasources, then your local smoke tests may work properly, but your geoprocessing functions will throw file not found errors in production."}),"\n",(0,n.jsx)(s.h2,{id:"integrate-with-seasketch",children:"Integrate With SeaSketch"}),"\n",(0,n.jsxs)(s.p,{children:["Once you've deployed your project, you will find a file called ",(0,n.jsx)(s.code,{children:"cdk.outputs"})," which contains the URL to the service manifest for your project."]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-json",children:'"restApiUrl": "https://xxxyyyyzzz.execute-api.us-west-2.amazonaws.com/prod/",\n'})}),"\n",(0,n.jsxs)(s.p,{children:["Now follow the ",(0,n.jsx)(s.a,{href:"https://docs.seasketch.org/seasketch-documentation/administrators-guide/sketch-classes",children:"SeaSketch instructions"})," to assign services to each of your sketch classes."]}),"\n",(0,n.jsxs)(s.p,{children:["If your sketch class is a Polygon or other feature type, you should assign it both a preprocessing function (for clipping) and a report client. If you installed the ",(0,n.jsx)(s.code,{children:"template-ocean-eez"})," starter template then your preprocessor is called ",(0,n.jsx)(s.code,{children:"clipToOceanEez"})," and report client is named ",(0,n.jsx)(s.code,{children:"MpaTabReport"}),"."]}),"\n",(0,n.jsx)(s.p,{children:"If your sketch class is a collection then you only need to assign it a report client. Since we build report clients that work on both individual sketches and sketch collections, you can assign the same report client to your collection as you assigned to your individual sketch class(es)."}),"\n",(0,n.jsx)(s.p,{children:"This should give you the sense that you can create different report clients for different sketch classes within the same project. Or even make reports for sketch collections completely different from reports for individual sketches."}),"\n",(0,n.jsx)(s.p,{children:"Create a sketch and run your reports to make sure it all works!"}),"\n",(0,n.jsx)(s.h2,{id:"build-up-your-tests",children:"Build Up Your Tests"}),"\n",(0,n.jsx)(s.p,{children:"Test different sketch and collection scenarios. Here's some possibilities:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"draw a sketch that covers the entire planning area"}),"\n",(0,n.jsx)(s.li,{children:"draw a tiny sketch"}),"\n",(0,n.jsx)(s.li,{children:"draw two sketches that overlap and put them in a collection. Make sure overlap is handled properly in reports."}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["When you find a sketch that produces an error in your reports in SeaSketch, in most cases you should be able to reproduce it in your local environment. To do this, export the sketch as a GeoJSON file, and put it in your ",(0,n.jsx)(s.code,{children:"examples/sketches"})," directory and run your smoke tests. If the geoprocessing functions all succeed, then load storybook and see if you can produce an error in the browser."]}),"\n",(0,n.jsx)(s.h2,{id:"additional-guides",children:"Additional Guides"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/preprocessing",children:"Create a custom preprocessing function"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/geoprocessing",children:"Learn more about geoprocessing functions"})}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/gip/GIP-1-i18n#language-translation-tutorial",children:"Setup language translation (i18n)"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Language translation takes effort to maintain. It is suggested that you get your reports close to final, in the English language, and then dig in."}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/workers",children:"Worker Functions"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sketchAttributes",children:"Custom Sketch Attributes"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/extraParams",children:"Extra Function Parameters"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/multiBoundary/",children:"Multi-Boundary"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/antimeridian",children:"Antimeridian"})}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"what-next",children:"What Next"}),"\n",(0,n.jsxs)(s.p,{children:["Still have more questions? ",(0,n.jsx)(s.a,{href:"https://github.com/seasketch/geoprocessing/discussions",children:"Start a discussion"})," on Github."]})]})}function h(e={}){const{wrapper:s}={...(0,t.R)(),...e.components};return s?(0,n.jsx)(s,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},28453:(e,s,r)=>{r.d(s,{R:()=>i,x:()=>l});var n=r(96540);const t={},o=n.createContext(t);function i(e){const s=n.useContext(o);return n.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function l(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:i(e.components),n.createElement(o.Provider,{value:s},e.children)}}}]);
\ No newline at end of file
+"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[31429],{76254:(e,s,r)=>{r.r(s),r.d(s,{assets:()=>c,contentTitle:()=>i,default:()=>h,frontMatter:()=>o,metadata:()=>l,toc:()=>a});var n=r(74848),t=r(28453);const o={},i="Create New Geoprocessing Project",l={id:"tutorials/newproject",title:"Create New Geoprocessing Project",description:"This tutorial walks you through designing and creating your own geoprocessing report. It covers many of the questions and decisions you might face along the way.",source:"@site/docs/tutorials/newproject.md",sourceDirName:"tutorials",slug:"/tutorials/newproject",permalink:"/geoprocessing/docs/next/tutorials/newproject",draft:!1,unlisted:!1,editUrl:"https://github.com/seasketch/geoprocessing/tree/main/website/templates/shared/docs/tutorials/newproject.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"Create Sample Project",permalink:"/geoprocessing/docs/next/tutorials/sampleproject"},next:{title:"Setup Existing Project",permalink:"/geoprocessing/docs/next/tutorials/existingproject"}},c={},a=[{value:"Where Do I Start?",id:"where-do-i-start",level:2},{value:"Design",id:"design",level:3},{value:"Start Simple",id:"start-simple",level:3},{value:"Create A SeaSketch Project",id:"create-a-seasketch-project",level:2},{value:"Initialize New Geoprocessing Project",id:"initialize-new-geoprocessing-project",level:2},{value:"Create Your First Report",id:"create-your-first-report",level:2},{value:"Build Your Project",id:"build-your-project",level:2},{value:"Deploy Project To AWS",id:"deploy-project-to-aws",level:2},{value:"Publish Datasources",id:"publish-datasources",level:2},{value:"Integrate With SeaSketch",id:"integrate-with-seasketch",level:2},{value:"Build Up Your Tests",id:"build-up-your-tests",level:2},{value:"Additional Guides",id:"additional-guides",level:2},{value:"What Next",id:"what-next",level:2}];function d(e){const s={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",ul:"ul",...(0,t.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(s.header,{children:(0,n.jsx)(s.h1,{id:"create-new-geoprocessing-project",children:"Create New Geoprocessing Project"})}),"\n",(0,n.jsx)(s.p,{children:"This tutorial walks you through designing and creating your own geoprocessing report. It covers many of the questions and decisions you might face along the way."}),"\n",(0,n.jsx)(s.p,{children:"This tutorial assumes:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Your ",(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/",children:"system setup"})," is complete"]}),"\n",(0,n.jsxs)(s.li,{children:["You completed the ",(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject",children:"sample project tutorial"})]}),"\n",(0,n.jsx)(s.li,{children:"Your geoprocessing virtual environment is currently running (Devcontainer or WSL)"}),"\n",(0,n.jsx)(s.li,{children:"You have VSCode open in your virtual environment with a terminal pane open"}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"where-do-i-start",children:"Where Do I Start?"}),"\n",(0,n.jsx)(s.p,{children:"Creating a geoprocessing project is not linear, it's iterative. You don't need to have all the answers for your project or understand all the features of the framework. Here's one approach:"}),"\n",(0,n.jsx)(s.h3,{id:"design",children:"Design"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:["Put together a rough design ",(0,n.jsx)(s.a,{href:"https://docs.google.com/document/d/1Qe7pZYmwg7ggRY9ocu3tpdTQkvuIHMr38wLxrjSitpU/edit?usp=sharing",children:"template"}),". This one asks common questions and is a good place to capture decisions."]}),"\n",(0,n.jsxs)(s.li,{children:["Explore the geoprocessing ",(0,n.jsx)(s.a,{href:"/storybook",children:"UI components library"}),"."]}),"\n",(0,n.jsx)(s.li,{children:"Look at other SeaSketch Reports and find ideas that match your needs."}),"\n"]}),"\n",(0,n.jsx)(s.h3,{id:"start-simple",children:"Start Simple"}),"\n",(0,n.jsx)(s.p,{children:"If it's not clear at this point, the geoprocessing framework is not a one-side-fits-all solution, it's a set of building blocks. Which ones you use are up to you."}),"\n",(0,n.jsx)(s.p,{children:"It takes time to figure out how it all works so keep it simple to start:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"single planning boundary or none at all"}),"\n",(0,n.jsx)(s.li,{children:"simple measurable objectives (overlap area, % area overlap, overlap feature count)"}),"\n",(0,n.jsx)(s.li,{children:"smaller datasets"}),"\n",(0,n.jsx)(s.li,{children:"no groupno classification of sketch types (e.g. protection levels)"}),"\n",(0,n.jsx)(s.li,{children:"no need to handle overlapping sketch polygons"}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["A good example of this is ",(0,n.jsx)(s.a,{href:"https://github.com/underbluewaters/oregon-next",children:"Oregon"})," SeaSketch reports."]}),"\n",(0,n.jsx)(s.p,{children:"Then as your planning process gets more complex:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"multiple planning boundaries (offshore/nearshore)"}),"\n",(0,n.jsx)(s.li,{children:"multiple objectives with targets"}),"\n",(0,n.jsx)(s.li,{children:"large datasets with multiple data subclasses."}),"\n",(0,n.jsx)(s.li,{children:"long running analysis with required precalculation"}),"\n",(0,n.jsx)(s.li,{children:"use of a sketch classification system (e.g. protection levels)"}),"\n",(0,n.jsx)(s.li,{children:"need to enforce rules about overlapping sketches"}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:"Your project can benefit from more advanced features:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"Geography"})," records representing project planning boundaries"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"Metric"})," records for representing multi-dimensional analysis results."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"Objective"})," records representing objective targets per sketch class."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"MetricGroup"})," records reresenting relationship of metric results to their data classes, datasource, objective target, etc."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"toolbox"})," for calculating overlay analysis metrics at the collection level in many dimensions - by data class, by protection level, by planning boundary.","\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"overlapFeatures"}),", ",(0,n.jsx)(s.code,{children:"rasterMetrics"}),", ",(0,n.jsx)(s.code,{children:"overlapFeaturesGroupMetrics"}),", ",(0,n.jsx)(s.code,{children:"overlapRasterGroupMetrics"})]}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(s.li,{children:["UI components that can work multi-dimensional metrics","\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"ClassTable"}),", ",(0,n.jsx)(s.code,{children:"SketchClassTable"}),", ",(0,n.jsx)(s.code,{children:"GeographySwitcher"})]}),"\n"]}),"\n"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"precalc"})," command automating pre-calculation of overlay stats for combinations of Datasources and Geographies."]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.code,{children:"worker"})," functions to run spread out geoprocessing work to run in parallel."]}),"\n",(0,n.jsxs)(s.li,{children:["Language ",(0,n.jsx)(s.code,{children:"translation"})," workflow and library of pre-translated UI components."]}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:"Examples of more complex projects:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://github.com/seasketch/california-reports",children:"California"})," - multiple geographies presented in reports (planning boundaries, bioregions), worker functions"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://github.com/seasketch/bermuda-reports-next",children:"Bermuda"})," - IUCN classification system with metrics calculated overall, per protection level, and per sketch. worker functions"]}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://github.com/seasketch/azores-nearshore-reports",children:"Blue Azores nearshore"})," - user switching between planning geographies."]}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"https://github.com/seasketch/samoa-reports",children:"Samoa Reports"})}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://github.com/seasketch/azores-nearshore-reports",children:"Azores Nearshore Reports"}),"."]}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"create-a-seasketch-project",children:"Create A SeaSketch Project"}),"\n",(0,n.jsxs)(s.p,{children:["First things first, follow the ",(0,n.jsx)(s.a,{href:"https://docs.seasketch.org/seasketch-documentation/administrators-guide/getting-started",children:"instructions"})," to create a new SeaSketch project. This includes defining the planning bounds and ",(0,n.jsx)(s.a,{href:"https://docs.seasketch.org/seasketch-documentation/administrators-guide/sketch-classes",children:"creating a Sketch class"}),". You will want to create a ",(0,n.jsx)(s.code,{children:"Polygon"})," sketch class with a name that makes sense for you project (e.g. MPA for Marine Protected Area) and then also a ",(0,n.jsx)(s.code,{children:"Collection"})," sketch class to group instances of your polygon sketch class into. Note that sketch classes are where you will integrate your geoprocessing services to view reports, but you will not do it at this time."]}),"\n",(0,n.jsx)(s.h2,{id:"initialize-new-geoprocessing-project",children:"Initialize New Geoprocessing Project"}),"\n",(0,n.jsx)(s.p,{children:"Start with initializing a new project:"}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-sh",children:"cd /workspaces\nnpx @seasketch/geoprocessing@7.0.0-beta.13 init 7.0.0-beta.13\n"})}),"\n",(0,n.jsx)(s.p,{children:"Tips:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"the answers to all of the init questions can be changed later, so don't worry if you don't know the answer."}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"https://github.com/seasketch/next/blob/master/LICENSE",children:"SeaSketch"})," uses a BSD-3 license (the default choice). You can choose any including ",(0,n.jsx)(s.code,{children:"UNLICENSED"}),' meaning proprietary or "All rights reserved" .']}),"\n",(0,n.jsxs)(s.li,{children:["The most common AWS region is ",(0,n.jsx)(s.code,{children:"us-west-1"})," or ",(0,n.jsx)(s.code,{children:"us-east-2"}),". ",(0,n.jsx)(s.a,{href:"https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html",children:"Choose the region"})," closest to your project."]}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["Learn more about your projects ",(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/structure",children:"structure"})]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject#create-git-repo",children:"Create Github repo and push"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/linkData",children:"Link data into workspace"})}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"create-your-first-report",children:"Create Your First Report"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject#reef-report",children:"Low-level vector report"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject#benthic-habitat-report",children:"High-level vector report"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject#seamount-report",children:"Low-level raster report"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sampleproject#coral-species-report",children:"High-level raster report"})}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"build-your-project",children:"Build Your Project"}),"\n",(0,n.jsxs)(s.p,{children:["The application ",(0,n.jsx)(s.code,{children:"build"})," proceess packages it for deployment. Specifically it:"]}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Checks all the Typescript code to make sure it's valid and types are used properly."}),"\n",(0,n.jsx)(s.li,{children:"Transpiles all Typescript to Javascript"}),"\n",(0,n.jsxs)(s.li,{children:["Bundles UI report clients into the ",(0,n.jsx)(s.code,{children:".build-web"})," directory"]}),"\n",(0,n.jsxs)(s.li,{children:["Bundles geoprocessing and preprocessing functions into the ",(0,n.jsx)(s.code,{children:".build"})," directory."]}),"\n"]}),"\n",(0,n.jsx)(s.p,{children:"To build your application run the following:"}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-bash",children:"npm run build\n"})}),"\n",(0,n.jsxs)(s.p,{children:["If the build step fails, you will need to look at the error message and figure out what you need to do. Did it fail in building the functions or the clients? 99% of the time you should be able to catch these errors sooner. If VSCode finds invalid Typescript code, it will warn you with files marked in ",(0,n.jsx)(s.code,{children:"red"})," in the Explorer panel or with red markes and squiggle text in any of the files."]}),"\n",(0,n.jsx)(s.p,{children:"If you're still not sure try some of the following:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Run your smoke tests, see if they pass"}),"\n",(0,n.jsx)(s.li,{children:"When was the last time your build did succeed? You can be sure the error is caused by a change you made since then either in your project code, by upgrading your geoprocessing library version and not migratin fully, or by changing something on your system."}),"\n",(0,n.jsx)(s.li,{children:"You can stash your current changes or commit them to a branch so they are not lost. Then sequentially check out previous commits of the code until you find one that builds properly. Now you know that the next commit cause the build error."}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"deploy-project-to-aws",children:"Deploy Project To AWS"}),"\n",(0,n.jsx)(s.p,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/deploy",children:"Deploy your project"})}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Setup AWSCLI"}),"\n",(0,n.jsx)(s.li,{children:"Deploy to AWS"}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"publish-datasources",children:"Publish Datasources"}),"\n",(0,n.jsxs)(s.p,{children:["Once you have deployed your project to AWS, it will have an S3 bucket just for publishing ",(0,n.jsx)(s.code,{children:"datasources"}),". The name of this bucket is based on the name of your project. If your project is named ",(0,n.jsx)(s.code,{children:"my-project"})," (the name assigned in your package.json file), then the bucket name will be:"]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{children:"s3://gp-my-project-datasets\n"})}),"\n",(0,n.jsx)(s.p,{children:"To pubish your data run the following command:"}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-bash",children:"npm run publish:data\n"})}),"\n",(0,n.jsx)(s.p,{children:"It will ask you if you want to publish all datasources, or choose from a list."}),"\n",(0,n.jsxs)(s.p,{children:["Your datasources will need to have already been imported using ",(0,n.jsx)(s.code,{children:"import:data"})," and exist in the ",(0,n.jsx)(s.code,{children:"data/dist"})," for this to work."]}),"\n",(0,n.jsx)(s.p,{children:"Note if you don't publish your datasources, then your local smoke tests may work properly, but your geoprocessing functions will throw file not found errors in production."}),"\n",(0,n.jsx)(s.h2,{id:"integrate-with-seasketch",children:"Integrate With SeaSketch"}),"\n",(0,n.jsxs)(s.p,{children:["Once you've deployed your project, you will find a file called ",(0,n.jsx)(s.code,{children:"cdk.outputs"})," which contains the URL to the service manifest for your project."]}),"\n",(0,n.jsx)(s.pre,{children:(0,n.jsx)(s.code,{className:"language-json",children:'"restApiUrl": "https://xxxyyyyzzz.execute-api.us-west-2.amazonaws.com/prod/",\n'})}),"\n",(0,n.jsxs)(s.p,{children:["Now follow the ",(0,n.jsx)(s.a,{href:"https://docs.seasketch.org/seasketch-documentation/administrators-guide/sketch-classes",children:"SeaSketch instructions"})," to assign services to each of your sketch classes."]}),"\n",(0,n.jsxs)(s.p,{children:["If your sketch class is a Polygon or other feature type, you should assign it both a preprocessing function (for clipping) and a report client. If you installed the ",(0,n.jsx)(s.code,{children:"template-ocean-eez"})," starter template then your preprocessor is called ",(0,n.jsx)(s.code,{children:"clipToOceanEez"})," and report client is named ",(0,n.jsx)(s.code,{children:"MpaTabReport"}),"."]}),"\n",(0,n.jsx)(s.p,{children:"If your sketch class is a collection then you only need to assign it a report client. Since we build report clients that work on both individual sketches and sketch collections, you can assign the same report client to your collection as you assigned to your individual sketch class(es)."}),"\n",(0,n.jsx)(s.p,{children:"This should give you the sense that you can create different report clients for different sketch classes within the same project. Or even make reports for sketch collections completely different from reports for individual sketches."}),"\n",(0,n.jsx)(s.p,{children:"Create a sketch and run your reports to make sure it all works!"}),"\n",(0,n.jsx)(s.h2,{id:"build-up-your-tests",children:"Build Up Your Tests"}),"\n",(0,n.jsx)(s.p,{children:"Test different sketch and collection scenarios. Here's some possibilities:"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"draw a sketch that covers the entire planning area"}),"\n",(0,n.jsx)(s.li,{children:"draw a tiny sketch"}),"\n",(0,n.jsx)(s.li,{children:"draw two sketches that overlap and put them in a collection. Make sure overlap is handled properly in reports."}),"\n"]}),"\n",(0,n.jsxs)(s.p,{children:["When you find a sketch that produces an error in your reports in SeaSketch, in most cases you should be able to reproduce it in your local environment. To do this, export the sketch as a GeoJSON file, and put it in your ",(0,n.jsx)(s.code,{children:"examples/sketches"})," directory and run your smoke tests. If the geoprocessing functions all succeed, then load storybook and see if you can produce an error in the browser."]}),"\n",(0,n.jsx)(s.h2,{id:"additional-guides",children:"Additional Guides"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/preprocessing",children:"Create a custom preprocessing function"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/geoprocessing",children:"Learn more about geoprocessing functions"})}),"\n",(0,n.jsxs)(s.li,{children:[(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/gip/GIP-1-i18n#language-translation-tutorial",children:"Setup language translation (i18n)"}),"\n",(0,n.jsxs)(s.ul,{children:["\n",(0,n.jsx)(s.li,{children:"Language translation takes effort to maintain. It is suggested that you get your reports close to final, in the English language, and then dig in."}),"\n"]}),"\n"]}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/workers",children:"Worker Functions"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/sketchAttributes",children:"Custom Sketch Attributes"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/tutorials/extraParams",children:"Extra Function Parameters"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/multiBoundary/",children:"Multi-Boundary"})}),"\n",(0,n.jsx)(s.li,{children:(0,n.jsx)(s.a,{href:"/geoprocessing/docs/next/antimeridian",children:"Antimeridian"})}),"\n"]}),"\n",(0,n.jsx)(s.h2,{id:"what-next",children:"What Next"}),"\n",(0,n.jsxs)(s.p,{children:["Still have more questions? ",(0,n.jsx)(s.a,{href:"https://github.com/seasketch/geoprocessing/discussions",children:"Start a discussion"})," on Github."]})]})}function h(e={}){const{wrapper:s}={...(0,t.R)(),...e.components};return s?(0,n.jsx)(s,{...e,children:(0,n.jsx)(d,{...e})}):d(e)}},28453:(e,s,r)=>{r.d(s,{R:()=>i,x:()=>l});var n=r(96540);const t={},o=n.createContext(t);function i(e){const s=n.useContext(o);return n.useMemo((function(){return"function"==typeof e?e(s):{...s,...e}}),[s,e])}function l(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:i(e.components),n.createElement(o.Provider,{value:s},e.children)}}}]);
\ No newline at end of file
diff --git a/assets/js/e6688bcd.1e0da28d.js b/assets/js/e6688bcd.27636be0.js
similarity index 99%
rename from assets/js/e6688bcd.1e0da28d.js
rename to assets/js/e6688bcd.27636be0.js
index 30441fc41..08a453bb6 100644
--- a/assets/js/e6688bcd.1e0da28d.js
+++ b/assets/js/e6688bcd.27636be0.js
@@ -1 +1 @@
-"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[21895],{88794:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>i,toc:()=>l});var s=t(74848),r=t(28453);const a={},o="Create Sample Project",i={id:"tutorials/sampleproject",title:"Create Sample Project",description:"This tutorial walks through creating a sample geoprocessing project for the Federated States of Micronesia. It demonstrates multiple methods for doing spatial analysis and creating reports, from low-level to high-level, so that you can engage with it at any/all of the levels needed for your project.",source:"@site/docs/tutorials/sampleproject.md",sourceDirName:"tutorials",slug:"/tutorials/sampleproject",permalink:"/geoprocessing/docs/next/tutorials/sampleproject",draft:!1,unlisted:!1,editUrl:"https://github.com/seasketch/geoprocessing/tree/main/website/templates/shared/docs/tutorials/sampleproject.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"System Setup",permalink:"/geoprocessing/docs/next/tutorials/"},next:{title:"Create New Project",permalink:"/geoprocessing/docs/next/tutorials/newproject"}},c={},l=[{value:"Initialize Geoprocessing Project",id:"initialize-geoprocessing-project",level:2},{value:"Create Git repo",id:"create-git-repo",level:2},{value:"Preprocessing",id:"preprocessing",level:2},{value:"Testing",id:"testing",level:3},{value:"Simple Report",id:"simple-report",level:2},{value:"simpleFunction",id:"simplefunction",level:3},{value:"SimpleReport",id:"simplereport",level:3},{value:"Language Translation",id:"language-translation",level:3},{value:"Generate Examples",id:"generate-examples",level:3},{value:"Run test suite",id:"run-test-suite",level:3},{value:"Storybook",id:"storybook",level:3},{value:"Simple Function Modifications",id:"simple-function-modifications",level:3},{value:"Simple Report Modification",id:"simple-report-modification",level:3},{value:"First Project Build",id:"first-project-build",level:3},{value:"Reef Report",id:"reef-report",level:2},{value:"Import Data",id:"import-data",level:3},{value:"Precalc Data",id:"precalc-data",level:3},{value:"Create Report",id:"create-report",level:3},{value:"Add to Tab Report",id:"add-to-tab-report",level:3},{value:"Benthic Habitat Report",id:"benthic-habitat-report",level:2},{value:"Import Data",id:"import-data-1",level:3},{value:"Precalc Data",id:"precalc-data-1",level:3},{value:"World Geography",id:"world-geography",level:3},{value:"Add Metric Group",id:"add-metric-group",level:3},{value:"Create Report",id:"create-report-1",level:3},{value:"Test New Example Sketch",id:"test-new-example-sketch",level:3},{value:"Add To Tab Report",id:"add-to-tab-report-1",level:3},{value:"Seamount Report",id:"seamount-report",level:2},{value:"Import Data",id:"import-data-2",level:3},{value:"Precalc Data",id:"precalc-data-2",level:3},{value:"Add Objective",id:"add-objective",level:3},{value:"Add Metric Group",id:"add-metric-group-1",level:3},{value:"Create Report",id:"create-report-2",level:3},{value:"Add To Tab Report",id:"add-to-tab-report-2",level:3},{value:"Data Complexity",id:"data-complexity",level:3},{value:"Coral Species Report",id:"coral-species-report",level:2},{value:"Import Data",id:"import-data-3",level:3},{value:"Precalc",id:"precalc",level:3},{value:"Add Metric Group",id:"add-metric-group-2",level:3},{value:"Add Objective",id:"add-objective-1",level:3},{value:"Create Report",id:"create-report-3",level:3},{value:"Add To Tab Report",id:"add-to-tab-report-3",level:3},{value:"Language Translation",id:"language-translation-1",level:3},{value:"Create Github Project",id:"create-github-project",level:2},{value:"What's Next",id:"whats-next",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components},{Details:a}=n;return a||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"create-sample-project",children:"Create Sample Project"})}),"\n",(0,s.jsx)(n.p,{children:"This tutorial walks through creating a sample geoprocessing project for the Federated States of Micronesia. It demonstrates multiple methods for doing spatial analysis and creating reports, from low-level to high-level, so that you can engage with it at any/all of the levels needed for your project."}),"\n",(0,s.jsx)(n.p,{children:"The planning area for this example is defined as extending from the Micronesia baseline (coastline/shoreline) to the outer boundary of the Exclusive Economic Zone (200 nautical miles)."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"EEZ with land",src:t(94616).A+"",width:"2113",height:"1099"})}),"\n",(0,s.jsx)(n.p,{children:"This tutorial assumes:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Your ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/tutorials/",children:"system setup"})," is complete"]}),"\n",(0,s.jsx)(n.li,{children:"Your geoprocessing virtual environment is running (Devcontainer or WSL)"}),"\n",(0,s.jsx)(n.li,{children:"You have VSCode open in your virtual environment with a terminal pane open"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Have questions along the way? Start a ",(0,s.jsx)(n.a,{href:"https://github.com/seasketch/geoprocessing/discussions",children:"discussion"})," on Github"]}),"\n",(0,s.jsx)(n.h2,{id:"initialize-geoprocessing-project",children:"Initialize Geoprocessing Project"}),"\n",(0,s.jsxs)(n.p,{children:["Start the project ",(0,s.jsx)(n.code,{children:"init"})," process, which will download the framework, and collect required project metadata."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:"cd /workspaces\nnpx @seasketch/geoprocessing@7.0.0-beta.12 init 7.0.0-beta.12\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"? Choose a name for your project\nfsm-reports-test\n? Please provide a short description of this project\nMicronesia reports\n? Source code repository location\n[LEAVE BLANK]\n? Your name\n[YOUR_NAME]\n? Your email\n[YOUR_EMAIL]\n? Organization name (optional)\nExample organization\n? What software license would you like to use?\nBSD-3-Clause\n? What AWS region would you like to deploy functions in?\nus-west-1\n? What languages will your reports be published in, other than English? (leave blank for none)\nChuukese\nKosraean\n"})}),"\n",(0,s.jsx)(n.p,{children:"After pressing Enter, your project will be created and all NodeJS software dependencies installed. If your language is not present, you will be able to add it later."}),"\n",(0,s.jsx)(n.p,{children:"Now, re-open VSCode one level deeper, in your project folder::"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"File -> Open Folder\nType /workspaces/fsm-reports-test/\nPress Ctrl-J or Ctrl-backtick to open a new terminal\n"})}),"\n",(0,s.jsx)(n.h2,{id:"create-git-repo",children:"Create Git repo"}),"\n",(0,s.jsx)(n.p,{children:"Before you continue, let's create a local git repository and commit everything so far as a starting point."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:'git init\ngit add .\ngit commit -m "first commit"\ngit branch -M main\n'})}),"\n",(0,s.jsxs)(n.p,{children:["After this point, you can continue using git commands in the terminal to stage code changes and commit them if that's what you know, or you can use VSCode's ",(0,s.jsx)(n.a,{href:"https://code.visualstudio.com/docs/sourcecontrol/overview",children:"built-in git support"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["To learn more about your projects folder structure, visit the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/structure",children:"structure"})," page."]}),"\n",(0,s.jsx)(n.h2,{id:"preprocessing",children:"Preprocessing"}),"\n",(0,s.jsx)(n.p,{children:'Preprocessing functions are invoked by the SeaSketch platform, on a user-drawn shape, right after the user finishes drawing it. It\'s a specialized function that validates a drawn shape and potentially modifies it, such as to remove portions of the shape outside the planning boundary. This "clipping" of the shape is useful in that it allows a user to overdraw beyond the planning boundary and it will be clipped right to the edge of that boundary.'}),"\n",(0,s.jsx)(n.p,{children:"Here is an example of a preprocessor clipping a user drawn polygon to erase any part overlapping with land"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Before Clip"}),(0,s.jsx)(n.th,{children:"After Clip"})]})}),(0,s.jsx)(n.tbody,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.img,{alt:"Before",src:t(58667).A+"",title:"Before",width:"885",height:"708"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.img,{alt:"After",src:t(41538).A+"",title:"After",width:"773",height:"618"})})]})})]}),"\n",(0,s.jsxs)(n.p,{children:["In the ",(0,s.jsx)(n.code,{children:"src/functions"})," directory you will find four preprocessing functions that come with every project, and they are further configureable to meet your needs:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"validatePolygon"})," - verifies shape is not self-crossing, is at least a certain size (default to 500 square meters) and no larger than a certain size (defaults to 1 million square kilometers)."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"clipToLand"})," - clips the shape to just the portion on land, as defined by OpenStreeMap land polygons. Includes validatePolygon."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"clipToOcean"})," - clips the shape to remove the portion on land, as defined by OpenStreetMap land polygons. Includes validatePolygon."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"clipToOceanEez"})," - clips the shape to keep the portion within the boundary from the coastline to the outer boundary of the EEZ. Includes validatePolygon."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["These functions use datasources published by the ",(0,s.jsx)(n.a,{href:"https://github.com/seasketch/global-datasources/tree/main",children:"global datasources"})," project. These datasources can be replaced with more authoritative ones in your own projects, or you can follow the instructions on the website to export the specific subset of the data relevant to your project, and import it directly into your project."]}),"\n",(0,s.jsx)(n.h3,{id:"testing",children:"Testing"}),"\n",(0,s.jsx)(n.p,{children:"Each preprocessing function has its own unit test and smoke test file. For example:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Unit: ",(0,s.jsx)(n.code,{children:"src/functions/validatePolygon.test.ts"})]}),"\n",(0,s.jsxs)(n.li,{children:["Smoke: ",(0,s.jsx)(n.code,{children:"src/functions/validatePolygonSmoke.test.ts"})]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Unit tests"})," ensure the preprocessor produces exact output for very specific input features and configuration, and throws errors properly."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Smoke tests"}),' are about ensuring the preprocessor behaves properly for your project location, and that its results "look right" for a variety of input features. It does this by loading example shapes from the project ',(0,s.jsx)(n.code,{children:"examples/features"})," directory. It then runs the preprocessing function on the examples, makes sure they produce output, and saves them to ",(0,s.jsx)(n.code,{children:"examples/output"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"To test your preprocessing functions, we need to create example features within the extent of our Micronesian planning area. To do this, run the following script:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:'npx tsx scripts/genRandomPolygon.ts --outDir examples/features --filename polygon1.json --bbox "[135.31244183762126,-1.1731109652985907,165.67652822599732,13.445432925389298]"\nnpx tsx scripts/genRandomPolygon.ts --outDir examples/features --filename polygon2.json --bbox "[135.31244183762126,-1.1731109652985907,165.67652822599732,13.445432925389298]"\n'})}),"\n",(0,s.jsxs)(n.p,{children:["This will output an example Feature and an example FeatureCollection to ",(0,s.jsx)(n.code,{children:"examples/features"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Now run the tests:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm test\n"})}),"\n",(0,s.jsxs)(n.p,{children:["You can now look at the geojson output files in the ",(0,s.jsx)(n.code,{children:"examples/output"})," directory, including visually by opening them in QGIS or pasting the JSON into geojson.io. This is the best way to visually verify the preprocessor worked as expected."]}),"\n",(0,s.jsx)(n.p,{children:"This is a good checkpoint to commit your latest changes to Github."}),"\n",(0,s.jsxs)(n.p,{children:["To learn more about preprocessing, check out the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/preprocessing",children:"guide"})]}),"\n",(0,s.jsx)(n.h2,{id:"simple-report",children:"Simple Report"}),"\n",(0,s.jsx)(n.p,{children:"Your new project comes with a simple report that calculates the area of a sketch or sketch collection and presents it in a human readable format. Let's look at the pieces that go into this report."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Simple Card View",src:t(44424).A+"",width:"964",height:"661"})}),"\n",(0,s.jsx)(n.h3,{id:"simplefunction",children:"simpleFunction"}),"\n",(0,s.jsxs)(n.p,{children:["The area calculation is done within a geoprocessing function in ",(0,s.jsx)(n.code,{children:"src/functions/simpleFunction.ts"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Geoprocessing functions are invoked by a report client, as soon as its loaded in the browser by SeaSketch. It's a specialized function that takes a Sketch polygon or collection of Sketch polygons, performs some analysis, and returns the result to be displayed in the report client."}),"\n",(0,s.jsxs)(n.p,{children:["Open ",(0,s.jsx)(n.code,{children:"src/functions/simpleFunction.ts"})," and you will notice this function defines a custom result payload called ",(0,s.jsx)(n.code,{children:"SimpleResults"}),", which in this case is a Javascript object with an ",(0,s.jsx)(n.code,{children:"area"})," property containing a number value."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"export interface SimpleResults {\n /** area of sketch within geography in square meters */\n area: number;\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"simpleFunction"})," starts off with the basic signature of a geoprocessing function. It accepts a ",(0,s.jsx)(n.code,{children:"sketch"})," parameter that is either a single ",(0,s.jsx)(n.code,{children:"Sketch"})," polygon or a ",(0,s.jsx)(n.code,{children:"SketchCollection"})," with multiple Sketch polygons. Unless your planning project only requires users to design single sketches and not collections, your geoprocessing function must be able to handle both."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"async function simpleFunction(\n sketch:\n | Sketch\n | SketchCollection,\n): Promise {\n"})}),"\n",(0,s.jsx)(n.p,{children:"The function then performs its analysis and returns the result."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"// Add analysis code\nconst sketchArea = area(sketch);\n\n// Custom return type\nreturn {\n area: sketchArea,\n};\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Below that, a new ",(0,s.jsx)(n.code,{children:"GeoprocessingHandler"})," is instantiated, with simpleFunction passed into it. Behind the scenes, this wraps simpleFunction in an AWS Lambda handler function, which once deployed to AWS, allows the geoprocessing function to be invoked using an API call, by a report client running in a web browser."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'export default new GeoprocessingHandler(simpleFunction, {\n title: "simpleFunction",\n description: "Function description",\n timeout: 60, // seconds\n memory: 1024, // megabytes\n executionMode: "async",\n});\n'})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"GeoprocessingHandler"})," requires a ",(0,s.jsx)(n.code,{children:"title"})," and ",(0,s.jsx)(n.code,{children:"description"}),", which uniquely identifies the function that will be published by your project. It also accepts some additional parameters defining what resources the Lamda should have, and its behavior:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"timeout"}),": how many seconds the Lambda will run before it times out in error."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"memory"}),": memory allocated to the Lambda, can go up to 10,240 MB. Number of processors increase with memory size automatically."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"executionMode"}),": determines how the report client waits for geoprocessing function results, defaults to async. Sync - wait with connection open for immediate results, Async - wait for web socket message that results are ready, then fetch. Sync should only be used for very fast geoprocessing functions (1-2 seconds max). Think of it as a performance optimization."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"You can change all these parameter values to suit your needs, but the default values are suitable for now."}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"simpleFunction"})," is already registered as a geoprocessing function in ",(0,s.jsx)(n.code,{children:"project/geoprocessing.json"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Now let's look at the browser report client that invokes this function."}),"\n",(0,s.jsx)(n.h3,{id:"simplereport",children:"SimpleReport"}),"\n",(0,s.jsxs)(n.p,{children:["A report client is a top-level React component for rendering a report in the users web browser. Report clients are located in the ",(0,s.jsx)(n.code,{children:"src/clients"})," directory and are responsible for the layout of one or more ",(0,s.jsx)(n.code,{children:"Card"})," components. Cards are able to invoke geoprocessing functions and display their results."]}),"\n",(0,s.jsx)(n.p,{children:"The two report clients that come with your project are:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"SimpleReport.tsx"})," - simple one page report client containing a SketchAttributesCard and a SimpleCard."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"TabReport.tsx"})," - more complex multi-page report layout controlled by a tab switcher component, so that only one page is in view at a time."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Both these report clients are already registered in ",(0,s.jsx)(n.code,{children:"project/geoprocessing.json"}),". To start, let's focus on ",(0,s.jsx)(n.code,{children:"SimpleReport"})," and ",(0,s.jsx)(n.code,{children:"SimpleCard"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:"export const SimpleReport = () => {\n return (\n \n \n \n \n );\n};\n"})}),"\n",(0,s.jsxs)(n.p,{children:["SimpleReport renders two cards, ",(0,s.jsx)(n.code,{children:"SimpleCard"})," and ",(0,s.jsx)(n.code,{children:"SketchAttributesCard"}),", wrapping them in a languge ",(0,s.jsx)(n.code,{children:"Translator"})," component (you will learn more about this later)."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"SketchAttributes"})," card is a card component that displays the properties of the users Sketch. No geoprocessing function is needed to do its work."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"SimpleCard"})," is a card component that invokes simpleFunction and displays its results. Let's look at it closer."]}),"\n",(0,s.jsxs)(n.p,{children:["The first thing to notice is that SimpleCard renders a ",(0,s.jsx)(n.code,{children:"ResultsCard"})," component. Behind the scenes ResultsCard invokes the geoprocessing function with the ",(0,s.jsx)(n.code,{children:"functionName"})," provided (simpleFunction). Keep in mind that in a production environment the ResultsCard is rendered in your web browser and the geoprocessing function is a Lambda function in Amazon's cloud invoked via an API call."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'\n'})}),"\n",(0,s.jsx)(n.p,{children:"ResultsCard then contains a render function that is provided with the results."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'{\n (data: SimpleResults) => {\n const areaSqKm = data.area / 1_000_000;\n const areaString = roundDecimalFormat(areaSqKm, 0, {\n keepSmallValues: true,\n });\n const sketchStr = isCollection ? t("sketch collection") : t("sketch");\n\n return (\n <>\n
\n \n This {{ sketchStr }} is {{ areaString }} square kilometers.\n \n
\n >\n );\n };\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["This render function takes an input parameter ",(0,s.jsx)(n.code,{children:"data"})," that has the same type (",(0,s.jsx)(n.code,{children:"SimpleResults"}),") as the return type of ",(0,s.jsx)(n.code,{children:"simpleFunction"}),". This gives you fully typed access to your report results."]}),"\n",(0,s.jsxs)(n.p,{children:["The code in this render function is the heart of each report card. This particular card takes the ",(0,s.jsx)(n.code,{children:"area"})," value it is given in square meters, and converts it to square kilometers. It then rounds it to a whole number, and formats it to make it more readable. Also notice that it renders a slightly different message depending on whether it is a single sketch or a sketch collection being reported on."]}),"\n",(0,s.jsx)(n.h3,{id:"language-translation",children:"Language Translation"}),"\n",(0,s.jsxs)(n.p,{children:["The last thing to notice is that SimpleCard contains a lot of boilerplate for language translation of its strings (using ",(0,s.jsx)(n.a,{href:"https://react.i18next.com/",children:(0,s.jsx)(n.code,{children:"react-i18next"})}),"). If your reports need to be multi-lingual you will need to to use these, otherwise you can drop them."]}),"\n",(0,s.jsx)(n.p,{children:"Language translation is a multi-part process:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["First, a combination of ",(0,s.jsx)(n.code,{children:"useTranslation"}),", ",(0,s.jsx)(n.code,{children:"t"})," function, and ",(0,s.jsx)(n.code,{children:"Trans"})," components are used to establish which strings in your report client and components should be translated."]}),"\n",(0,s.jsxs)(n.li,{children:["Next, those translateable strings are extracted using the ",(0,s.jsx)(n.code,{children:"extract:translation"})," command, and output to ",(0,s.jsx)(n.code,{children:"src/i18n/lang/en/translation.json"}),". The strings extracted for SimpleCard are:"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:'{\n "sketch": "sketch",\n "sketch collection": "sketch collection",\n "SimpleCard sketch size message": "This {{sketchStr}} is {{areaString}} square kilometers.",\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"The English translation file is then translated to the other needed languages and put into their own translation files"}),"\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.code,{children:"Translator"})," component in your report client is then responsible for inspecting the users language at runtime in the browser and swapping the English strings for strings in the appropriate language."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This process is covered in more detail in a separate doc."}),"\n",(0,s.jsx)(n.h3,{id:"generate-examples",children:"Generate Examples"}),"\n",(0,s.jsxs)(n.p,{children:["With a working geoprocessing function and report client already in place, you're ready to generate example sketches for testing them. We'll use the same ",(0,s.jsx)(n.code,{children:"genRandomPolygon"})," script as before. But let's look closer at how we figured out the bounding box extent of the Micronesian planning area. First, use ogrinfo to inspect the Micronesia EEZ polygon data layer in your data package."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"ogrinfo -so -json data/src/eez_withland_mr.fgb\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Deep in its output you will see a ",(0,s.jsx)(n.code,{children:"geometryFields"})," property, which contains the bounding box extent of the EEZ feature. Use the ",(0,s.jsx)(n.code,{children:"jq"})," utility to extract this extent:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"ogrinfo -so -json data/src/eez_withland_mr.fgb | jq -c .layers[0].geometryFields[0].extent\n[135.31244183762126,-1.1731109652985907,165.67652822599732,13.445432925389298]\n"})}),"\n",(0,s.jsx)(n.p,{children:"This will output an array with the extent of the EEZ. This is just one of multiple possible methods to get this extent. You are welcome to use the method that works best for you."}),"\n",(0,s.jsx)(n.p,{children:"Now run the genRandomPolygon script with this extent. The following examples will create a Sketch polygon, and then a SketchCollection containing 10 Sketch polygons."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:'npx tsx scripts/genRandomPolygon.ts --outDir examples/sketches --filename sketch1.json --bbox "[135.31244183762126,-1.1731109652985907,165.67652822599732,13.445432925389298]" --bboxShrinkFactor 5 --sketch\nnpx tsx scripts/genRandomPolygon.ts --outDir examples/sketches --filename sketchCollection1.json --bbox "[135.31244183762126,-1.1731109652985907,165.67652822599732,13.445432925389298]" --bboxShrinkFactor 5 --sketch --numFeatures 10\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"--bboxShrinkFactor"})," argument used shrinks the height and width of the given bbox by a factor of 5, and then generates random features that are within that reduced bbox. A suitable shrink factor value was discovered through trial and error. Simply visualize the resulting json file in QGIS or other software and find a value that produces polygons that are completely within the planning area polygon. (see image below)."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.img,{alt:"EEZ bbox",src:t(81465).A+"",width:"1278",height:"661"}),"\nImage: cluster of 10 random sketches (in orange) within Micronesia EEZ"]}),"\n",(0,s.jsxs)(n.p,{children:["Learn more about the options for ",(0,s.jsx)(n.code,{children:"genRandomPolygon"})," by running:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"npx tsx scripts/genRandomPolygon.ts --help\n"})}),"\n",(0,s.jsx)(n.h3,{id:"run-test-suite",children:"Run test suite"}),"\n",(0,s.jsxs)(n.p,{children:["Now that you have example features and sketches, you can test ",(0,s.jsx)(n.code,{children:"simpleFunction"}),". Run the test suite now:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm test\n"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Using ",(0,s.jsx)(n.code,{children:"simpleFunctionSmoke.test.ts"}),", simpleFunction will be run against all of the polygon Sketches in ",(0,s.jsx)(n.code,{children:"examples/sketches"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["The results of all smokes tests are output to the ",(0,s.jsx)(n.code,{children:"examples/output"})," directory."]}),"\n",(0,s.jsx)(n.li,{children:"You can inspect the output files, and see the calculated area values for each sketch input."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Commit the output files to your git repository at this time."}),"\n",(0,s.jsxs)(n.p,{children:["You can make changes to simpleFunction, then rerun tests to regenerate them at any time, and delete any that are stale and no longer needed. For advanced use, check out the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/testing",children:"testing"})," guide."]}),"\n",(0,s.jsx)(n.h3,{id:"storybook",children:"Storybook"}),"\n",(0,s.jsx)(n.p,{children:"Storybook is used to view your reports."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run storybook\n"})}),"\n",(0,s.jsx)(n.p,{children:"This will:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Generate a story for every combination of report client registered in ",(0,s.jsx)(n.code,{children:"project/geoprocessing.json"})," and sketch present in ",(0,s.jsx)(n.code,{children:"examples/sketches"}),"."]}),"\n",(0,s.jsx)(n.li,{children:"Load all of the smoke test output for every sketch (to load in stories instead of running geoprocessing functions)"}),"\n",(0,s.jsx)(n.li,{children:"Start the storybook server and give you the URL."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Open the storybook URL in your browser and click through the stories."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Simple Card View",src:t(44424).A+"",width:"964",height:"661"})}),"\n",(0,s.jsx)(n.p,{children:"A powerful feature of Storybook is that when you save edits to your report client or component code, storybook will refresh the browser automatically with the changes. This lets you develop your reports and debug them more quickly."}),"\n",(0,s.jsx)(n.p,{children:"There are a couple of situations that will cause you to need to stop your storybook server (Ctrl-C) and then restart it to pick up the changes."}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["you add more sketch examples to your ",(0,s.jsx)(n.code,{children:"examples/sketch"})," directory"]}),"\n",(0,s.jsx)(n.li,{children:"you rerun smoke tests and generate new test output"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Learn more in the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/tutorials/storybook",children:"storybook guide"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"simple-function-modifications",children:"Simple Function Modifications"}),"\n",(0,s.jsx)(n.p,{children:"Let's enhance your simple geoprocessing function to calculate more detailed information when the report is run on a sketch collection. It should calculate the area of the entire collection, and the area of each child sketch in the collection."}),"\n",(0,s.jsxs)(n.p,{children:["First modify SimpleResults with an additional property ",(0,s.jsx)(n.code,{children:"childSketchAreas"})," that can store this information:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"export interface SimpleResults {\n /** area of reef within sketch in square meters */\n area: number;\n childSketchAreas: {\n /** Name of the sketch */\n name: string;\n /** Area of the sketch in square meters */\n area: number;\n }[];\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Then calculate the additional values and return them in the result payload:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// Add analysis code\nconst sketchArea = area(sketch);\n\nlet childSketchAreas: SimpleResults["childSketchAreas"] = [];\nif (sketch.properties.isCollection) {\n childSketchAreas = toSketchArray(sketch).map((sketch) => ({\n name: sketch.properties.name,\n area: area(sketch),\n }));\n}\n\n// Custom return type\nreturn {\n area: sketchArea,\n childSketchAreas,\n};\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Here's what the final ",(0,s.jsx)(n.code,{children:"simpleFunction"})," code should look like:"]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/functions/simpleFunction.ts"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'import {\n Sketch,\n SketchCollection,\n Polygon,\n MultiPolygon,\n GeoprocessingHandler,\n toSketchArray,\n} from "@seasketch/geoprocessing";\nimport { area } from "@turf/turf";\n\nexport interface SimpleResults {\n /** area of reef within sketch in square meters */\n area: number;\n childSketchAreas: {\n /** Name of the sketch */\n name: string;\n /** Area of the sketch in square meters */\n area: number;\n }[];\n}\n\n/**\n * Simple geoprocessing function with custom result payload\n */\nasync function simpleFunction(\n sketch:\n | Sketch\n | SketchCollection,\n): Promise {\n // Add analysis code\n const sketchArea = area(sketch);\n\n let childSketchAreas: SimpleResults["childSketchAreas"] = [];\n if (sketch.properties.isCollection) {\n childSketchAreas = toSketchArray(sketch).map((sketch) => ({\n name: sketch.properties.name,\n area: area(sketch),\n }));\n }\n\n // Custom return type\n return {\n area: sketchArea,\n childSketchAreas,\n };\n}\n\nexport default new GeoprocessingHandler(simpleFunction, {\n title: "simpleFunction",\n description: "Function description",\n timeout: 60, // seconds\n memory: 1024, // megabytes\n executionMode: "async",\n});\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Run your tests again to generate the new smoke test output:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run test\n"})}),"\n",(0,s.jsx)(n.h3,{id:"simple-report-modification",children:"Simple Report Modification"}),"\n",(0,s.jsxs)(n.p,{children:["Now let's modify SimpleReportCard to display the new data. You will add a new ",(0,s.jsx)(n.code,{children:"Collapse"})," section with a ",(0,s.jsx)(n.code,{children:"Table"})," component that lists out the sketch areas by name."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:'
\n \n This {{ sketchStr }} is {{ areaString }} km\xb2.\n \n
\n{isCollection && (\n \n
\n roundDecimalFormat(row.area / 1_000_000, 0, {\n keepSmallValues: true,\n }),\n },\n ]}\n />\n \n)}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Here's what the final SimpleCard code should look like:"}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/components/SimpleCard.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:'import React from "react";\nimport { Trans, useTranslation } from "react-i18next";\nimport {\n Collapse,\n ResultsCard,\n Table,\n useSketchProperties,\n} from "@seasketch/geoprocessing/client-ui";\n// Import SimpleResults to type-check data access in ResultsCard render function\nimport { SimpleResults } from "../functions/simpleFunction.js";\nimport { roundDecimalFormat } from "@seasketch/geoprocessing/client-core";\n\nexport const SimpleCard = () => {\n const { t } = useTranslation();\n const [{ isCollection }] = useSketchProperties();\n const titleTrans = t("SimpleCard title", "Simple Report");\n return (\n <>\n \n {(data: SimpleResults) => {\n const areaSqKm = data.area / 1_000_000;\n const areaString = roundDecimalFormat(areaSqKm, 0, {\n keepSmallValues: true,\n });\n const sketchStr = isCollection ? t("sketch collection") : t("sketch");\n\n return (\n <>\n
\n \n This {{ sketchStr }} is {{ areaString }} km\xb2.\n \n
\n {isCollection && (\n \n
\n roundDecimalFormat(row.area / 1_000_000, 0, {\n keepSmallValues: true,\n }),\n },\n ]}\n />\n \n )}\n >\n );\n }}\n \n >\n );\n};\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"If your storybook is still running from last time, you will need to restart it to pick up the new smoke test output."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"Ctrl-C\nnpm run storybook\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Your updated report should have a new collapsible table, that when expanded looks like the following:\n",(0,s.jsx)(n.img,{alt:"Simple Card with table",src:t(51899).A+"",width:"747",height:"679"})]}),"\n",(0,s.jsx)(n.h3,{id:"first-project-build",children:"First Project Build"}),"\n",(0,s.jsxs)(n.p,{children:["Now that you have confirmed your function is working properly, and your report client displays properly for a variety of example sketches, you are ready to do your first build. The application ",(0,s.jsx)(n.code,{children:"build"})," proceess packages it for deployment. Specifically it:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Checks all the Typescript code to make sure it's valid and types are used properly."}),"\n",(0,s.jsx)(n.li,{children:"Transpiles all Typescript to Javascript"}),"\n",(0,s.jsxs)(n.li,{children:["Bundles UI report clients into the ",(0,s.jsx)(n.code,{children:".build-web"})," directory"]}),"\n",(0,s.jsxs)(n.li,{children:["Bundles geoprocessing and preprocessing functions into the ",(0,s.jsx)(n.code,{children:".build"})," directory."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"To build your application run the following:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run build\n"})}),"\n",(0,s.jsx)(n.p,{children:"Once your build is successful, you should stage and commit all your changes to git."}),"\n",(0,s.jsx)(n.h2,{id:"reef-report",children:"Reef Report"}),"\n",(0,s.jsx)(n.p,{children:"Next you will create a coral reef report that uses the reef extent dataset. Here is an image of it displayed in QGIS. Notice that the coral is entirely in shallow water around the island coastline and atolls."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Reef Extent",src:t(35162).A+"",width:"2053",height:"1024"})}),"\n",(0,s.jsx)(n.h3,{id:"import-data",children:"Import Data"}),"\n",(0,s.jsx)(n.p,{children:"To access this datasource, first download a data package prepared for FSM to your project space and unzip it:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"wget -P data/src https://github.com/user-attachments/files/18030075/FSM_MSP_Data_Example_v2.zip\nunzip data/src/FSM_MSP_Data_Example_v2.zip -d data/src\nrm data/src/FSM_MSP_Data_Example_v2.zip\n"})}),"\n",(0,s.jsx)(n.p,{children:"Now import the datasource to your project."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run import:data\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"? Type of data?\nVector\n? Enter path to src file (with filename)\ndata/src/reefextent.fgb\n? Select layer to import\nreefextent\n? Choose unique datasource name (a-z, A-Z, 0-9, -, _), defaults to filename\nreefextent\n? Should multi-part geometries be split into single-part geometries?\nYes\n? (Optional) additional formats to create (besides fgb)\n[Press enter to skip]\n? Select feature properties that you want to group metrics by\n[Press enter to skip]\n? Select additional feature properties to keep in final datasource\n[Press enter to skip]\n? These formats are automatically created: fgb. Select any additional formats you want created\n[Press enter to skip]\n? Will you be precalculating summary metrics for this datasource after import? (Typically yes if reporting sketch % overlap with datasource)\nYes\n"})}),"\n",(0,s.jsx)(n.p,{children:"The import process will:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"reproject your data to the WGS84 reference system, if not already (required by Turf.JS)"}),"\n",(0,s.jsxs)(n.li,{children:["split any features that cross the 180 degree ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/antimeridian",children:"antimeridian"})]}),"\n",(0,s.jsx)(n.li,{children:"reduce the source dataset down to only the necessary attributes (saving network bandwidth later)"}),"\n",(0,s.jsxs)(n.li,{children:["output a new file in the cloud-optimized flatgeobuf format to the ",(0,s.jsx)(n.code,{children:"data/dist"})," directory."]}),"\n",(0,s.jsxs)(n.li,{children:["register the datasource in ",(0,s.jsx)(n.code,{children:"project/datasources.json"}),", along with additional metadata. This allows you to:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["quickly access project datasources in your reports using the ",(0,s.jsx)(n.code,{children:"projectClient"})," (more on this later)"]}),"\n",(0,s.jsxs)(n.li,{children:["quickly reimport datasources using the ",(0,s.jsx)(n.code,{children:"reimport:data"})," command, without having to answer questions again."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Once the import is finished, you are ready to use your datasources for ",(0,s.jsx)(n.code,{children:"local"})," report development. You can add, edit, or delete records in datasources.json manually to meet your need as long as the records meet the expected ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/concepts#datasources",children:"schema"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"precalc-data",children:"Precalc Data"}),"\n",(0,s.jsx)(n.p,{children:"Next, you will create a standalone script to calculate the total area of the polygons in the reef extent datasource for use in the report. By doing this calculation ahead of time, you won't need to do it every time your geoprocessing function runs. There is an automated way of precalculating the area of a datasource, but the purpose of this is to teach you a workflow for doing it on your own."}),"\n",(0,s.jsxs)(n.p,{children:["Create a new file with the following code and save it to ",(0,s.jsx)(n.code,{children:"scripts/coralReefPrecalc.ts"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// Run the following command from the project root directory\n// npx tsx scripts/coralReefPrecalc.ts\n\nimport { area } from "@turf/turf";\nimport { geojson } from "flatgeobuf";\nimport { readFileSync } from "fs";\nimport fs from "fs-extra";\n\n// Fetch all reef features and calculate total area\nconst buffer = readFileSync(\n `${import.meta.dirname}/../data/dist/reefextent.fgb`,\n);\nconst reefFeatures = geojson.deserialize(new Uint8Array(buffer));\nconst totalArea = area(reefFeatures);\n\nconst reefPrecalc = {\n totalAreaSqMeters: totalArea,\n};\n\nfs.ensureDirSync(`${import.meta.dirname}/../data/precalc`);\nfs.writeJsonSync(\n `${import.meta.dirname}/../data/precalc/reefextent.json`,\n reefPrecalc,\n);\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Now run it. Your shell needs to be in the root directory of your project to run this Typescript file directly using ",(0,s.jsx)(n.code,{children:"npx"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npx tsx scripts/coralReefPrecalc.ts\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The script fetches all features from the reef extent flatgeobuf file, calculates their total area and writes it to ",(0,s.jsx)(n.code,{children:"data/precalc/reefextent.json"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:'{\n "totalArea": 716100906.2570591\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"We are going to use this precalculated value in a geoprocessing function in the next step."}),"\n",(0,s.jsx)(n.h3,{id:"create-report",children:"Create Report"}),"\n",(0,s.jsx)(n.p,{children:"To create a blank report ready to build on, run the following:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run create:report\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"? Type of report to create\nBlank report - empty report ready to build from scratch\n\n? Describe what this reports geoprocessing function will calculate (e.g. Calculate sketch overlap with boundary polygons)\nCalculate sketch overlap with reef extent datasource\n\n? Title for this report, in camelCase\ncoralReef\n\n\u2714 Created coralReef report\n\u2714 Registered report assets in project/geoprocessing.json\n\nGeoprocessing function: src/functions/coralReef.ts\nSmoke test: src/functions/coralReefSmoke.test.ts\nReport component: src/components/CoralReefCard.tsx\nStory generator: src/components/CoralReefCard.example-stories.ts\n\nNext Steps:\n * 'npm test' to run smoke tests against your new geoprocessing function\n * 'npm run storybook' to view your new report with smoke test output\n * Add to a top-level report client or page when ready\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Open ",(0,s.jsx)(n.code,{children:"src/functions/coralReef.ts"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"You will now update this code to answer the following questions:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"What percentage of all coral reef is within the current sketch polygon (or sketch collection polygons)?"}),"\n",(0,s.jsx)(n.li,{children:"If it is a sketch collection, does it meet the planning objective of protecting 20% of all coral reef?"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Replace the existing code with the following:"}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/functions/coralReef.ts"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'import {\n Sketch,\n SketchCollection,\n Polygon,\n MultiPolygon,\n GeoprocessingHandler,\n getFeaturesForSketchBBoxes,\n toSketchArray,\n clipMultiMerge,\n isSketchCollection,\n clip,\n Feature,\n} from "@seasketch/geoprocessing";\nimport project from "../../project/projectClient.js";\nimport { area, featureCollection } from "@turf/turf";\nimport reefPrecalc from "../../data/precalc/reefextent.json";\n\nexport interface CoralReefResults {\n /** area of all reef extent polygons in square meters */\n totalArea: number;\n /** area of reef extent within sketch or sketch collection in square meters */\n sketchArea: number;\n childSketchAreas: {\n /** Name of the sketch */\n name: string;\n /** Area of reef extent within child sketch in square meters */\n area: number | null;\n }[];\n}\n\n/**\n * Simple geoprocessing function with custom result payload\n */\nexport async function coralReef(\n sketch:\n | Sketch\n | SketchCollection,\n): Promise {\n // Load just the reef features that intersect with the sketch bounding box\n // or in case of a sketch collection, the child sketch bounding boxes\n const ds = project.getInternalVectorDatasourceById("reefextent");\n const url = project.getDatasourceUrl(ds);\n const reefFeatures: Feature[] =\n await getFeaturesForSketchBBoxes(sketch, url);\n\n // Add analysis code\n\n // If collection, calculate area of each sketches intersection with reef\n let childSketchAreas: CoralReefResults["childSketchAreas"] = [];\n if (sketch.properties.isCollection) {\n childSketchAreas = toSketchArray(sketch).map((sketch) => {\n const sketchReefOverlap =\n reefFeatures.length > 0\n ? clipMultiMerge(\n sketch,\n featureCollection(reefFeatures),\n "intersection",\n )\n : null;\n return {\n name: sketch.properties.name,\n area: sketchReefOverlap ? area(sketchReefOverlap) : 0,\n };\n });\n }\n\n // Calculate area of overall sketch intersection with reef\n const sketchArea = (() => {\n // Figure out feature to clip\n let clipFeature: Feature | null;\n if (reefFeatures.length === 0) {\n return 0;\n } else if (isSketchCollection(sketch)) {\n // union sketches to remove overlap and avoid double count\n clipFeature = clip(sketch, "union");\n if (!clipFeature) return 0;\n } else {\n clipFeature = sketch;\n }\n //Merge reefFeatures into a single multipolygon, then intersect\n const sketchReefOverlap =\n reefFeatures.length > 0\n ? clipMultiMerge(\n clipFeature,\n featureCollection(reefFeatures),\n "intersection",\n )\n : null;\n return sketchReefOverlap ? area(sketchReefOverlap) : 0;\n })();\n\n // Custom return type\n return {\n totalArea: reefPrecalc.totalAreaSqMeters,\n sketchArea: sketchArea,\n childSketchAreas,\n };\n}\n\nexport default new GeoprocessingHandler(coralReef, {\n title: "coralReef",\n description: "calculate sketch overlap with reef extent datasource",\n timeout: 60, // seconds\n memory: 1024, // megabytes\n executionMode: "async",\n});\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Notice that the code imports the totalArea value you precalculated and inserts it into the result payload, avoiding the need to recalculate it each time."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'import reefPrecalc from "../../data/precalc/reefextent.json";\n\nreefPrecalc.totalArea;\n'})}),"\n",(0,s.jsx)(n.p,{children:"Then it fetches only the reef features whose bounding box intersects with the sketch bounding box, or in case of a sketch collection, that intersects with each of its child sketch bounding boxes. This is more efficient than fetching the entire reef dataset, saving time and network bandwidth."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// Load just the reef features that intersect with the sketch bounding box\n// or in case of a sketch collection, the child sketch bounding boxes\nconst ds = project.getInternalVectorDatasourceById("reefextent");\nconst url = project.getDatasourceUrl(ds);\nconst reefFeatures: Feature[] =\n await getFeaturesForSketchBBoxes(sketch, url);\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Next, if the sketch is a collection, it calculates how much coral reef overlaps with each individual sketch. To do this, it needs to figure out the areas where the sketches and coral reef ",(0,s.jsx)(n.code,{children:"intersect"}),". This is calculated using the ",(0,s.jsx)(n.code,{children:"clipMultiMerge"})," function. It is essential that this function is used because it merges the reefFeatures collection into a single multipolygon before intersecting it with the sketch. If you were to use the ",(0,s.jsx)(n.code,{children:"clip"})," function you would need to loop through each reef feature and clip the sketch to it."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// If collection, calculate area of each sketches intersection with reef\nlet childSketchAreas: CoralReefResults["childSketchAreas"] = [];\nif (sketch.properties.isCollection) {\n childSketchAreas = toSketchArray(sketch).map((sketch) => {\n const sketchReefOverlap =\n reefFeatures.length > 0\n ? clipMultiMerge(\n sketch,\n featureCollection(reefFeatures),\n "intersection",\n )\n : null;\n return {\n name: sketch.properties.name,\n area: sketchReefOverlap ? area(sketchReefOverlap) : 0,\n };\n });\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Finally, it calculates how much coral reef overlaps with the entire sketch/collection."}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"If there is no overlap between the reef and sketch, then it simply returns zero."}),"\n",(0,s.jsxs)(n.li,{children:["If it's a sketch collection it first performs a ",(0,s.jsx)(n.code,{children:"union"})," operation that merges all of the sketches into a single Multipolygon, dissolving any overlap between the sketches so that area is not double counted."]}),"\n",(0,s.jsx)(n.li,{children:"If it's a single sketch polygon then it just calculates its area and returns it."}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// Calculate area of overall sketch intersection with reef\nconst sketchArea = (() => {\n // Figure out feature to clip\n let clipFeature: Feature | null;\n if (reefFeatures.length === 0) {\n return 0;\n } else if (isSketchCollection(sketch)) {\n // union sketches to remove overlap and avoid double count\n clipFeature = clip(sketch, "union");\n if (!clipFeature) return 0;\n } else {\n clipFeature = sketch;\n }\n //Merge reefFeatures into a single multipolygon, then intersect\n const sketchReefOverlap =\n reefFeatures.length > 0\n ? clipMultiMerge(\n clipFeature,\n featureCollection(reefFeatures),\n "intersection",\n )\n : null;\n return sketchReefOverlap ? area(sketchReefOverlap) : 0;\n})();\n\n// Custom return type\nreturn {\n totalArea: reefPrecalc.totalAreaSqMeters,\n sketchArea: sketchArea,\n childSketchAreas,\n};\n'})}),"\n",(0,s.jsx)(n.p,{children:"Now run tests to generate updated output for each of the sample sketches:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run test\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Confirm that the output looks as expected. It is possible none of your test sketches will overlap with any coral reef features in which case all area values will have a ",(0,s.jsx)(n.code,{children:"0"})," value."]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"examples/output/sketchCollection1/coralReef.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:'{\n "totalArea": 716100906.2570591,\n "sketchArea": 367734.86730626615,\n "childSketchAreas": [\n {\n "name": "sketchCollection1-1",\n "area": 428611581.5348215\n },\n {\n "name": "sketchCollection1-2",\n "area": 258701691.8012635\n },\n {\n "name": "sketchCollection1-3",\n "area": 599831752.2377243\n },\n {\n "name": "sketchCollection1-4",\n "area": 372585470.74404347\n },\n {\n "name": "sketchCollection1-5",\n "area": 562781719.588172\n },\n {\n "name": "sketchCollection1-6",\n "area": 528237794.83984125\n },\n {\n "name": "sketchCollection1-7",\n "area": 253970548.59694752\n },\n {\n "name": "sketchCollection1-8",\n "area": 376674659.1741572\n },\n {\n "name": "sketchCollection1-9",\n "area": 657788539.6501052\n },\n {\n "name": "sketchCollection1-10",\n "area": 712233449.0549812\n }\n ]\n}\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Now open src/components/CoralReefCard.tsx."}),"\n",(0,s.jsx)(n.p,{children:"You will now update this code to:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Display the % of total coral reef captured within this sketch"}),"\n",(0,s.jsxs)(n.li,{children:["If it is a sketch collection","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Indicate whether the objective of protecting 20% of all coral reef has been met."}),"\n",(0,s.jsx)(n.li,{children:"Display a collapsible area with a breakdown of the area and % area of coral reef within each individual sketch in the collection."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Replace the existing code with the following:"}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/components/CoralReefCard.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-javascript",children:'import React from "react";\nimport { Trans, useTranslation } from "react-i18next";\nimport {\n ResultsCard,\n useSketchProperties,\n HorizontalStackedBar,\n Collapse,\n Table,\n ObjectiveStatus,\n VerticalSpacer,\n} from "@seasketch/geoprocessing/client-ui";\nimport {\n percentWithEdge,\n roundDecimalFormat,\n squareMeterToKilometer,\n} from "@seasketch/geoprocessing/client-core";\n\n// Import CoralReefResults to type-check data access in ResultsCard render function\nimport { CoralReefResults } from "../functions/coralReef.js";\n\nexport const CoralReefCard = () => {\n const { t } = useTranslation();\n const [{ isCollection }] = useSketchProperties();\n const titleTrans = t("CoralReefCard title", "Coral Reef");\n return (\n <>\n \n {(data: CoralReefResults) => {\n const target = 0.2; // 20%\n const reefPerc = data.sketchArea / data.totalArea;\n const reefPercString = percentWithEdge(reefPerc);\n const targetPercString = percentWithEdge(target);\n\n const meetsObjective = reefPerc >= target;\n\n // Adjust values for chart to be in range 0-100\n const chartRows = [[[reefPerc * 100]]];\n\n const sketchTypeStr = isCollection\n ? t("sketch collection")\n : t("sketch");\n\n const meetsOrNotElement = meetsObjective ? (\n \n This {{ sketchTypeStr }} meets the objective of protecting{" "}\n {{ targetPercString }} of coral reef\n \n ) : (\n \n This {{ sketchTypeStr }} does not meet the objective of protecting{" "}\n {{ targetPercString }} of coral reef\n \n );\n\n return (\n <>\n
\n \n {{ reefPercString }} of all Micronesia coral reef is within\n this {{ sketchTypeStr }}.\n \n
\n roundDecimalFormat(squareMeterToKilometer(row.area)),\n },\n {\n Header: t("% Reef within Sketch"),\n accessor: (row: any) =>\n percentWithEdge(row.area / data.totalArea),\n },\n ]}\n />\n \n )}\n >\n );\n }}\n \n >\n );\n};\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"There are multiple things worth noticing:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"squareMeterToKilometer"})," conversion helper function is used"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"percentWithEdge"})," and ",(0,s.jsx)(n.code,{children:"roundDecimalFormat"})," helper functions are used to format values to be more human readable. Will use locale settings of the users browser when formatting decimal and percent."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"HorizontalStackedBar"})," and ",(0,s.jsx)(n.code,{children:"ObjectiveStatus"})," core UI components present information in a more visually interesting way that can be reused across reports. See core ",(0,s.jsx)(n.a,{href:"/storybook",children:"storybook"})," for more examples of their use."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Now, start storybook and view the result. You will find the CoralReefCard under the ",(0,s.jsx)(n.code,{children:"Components"})," section:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run storybook\n"})}),"\n",(0,s.jsx)(n.p,{children:"When viewing a sketch example, it should display the following:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CoralReefCard sketch view",src:t(8847).A+"",width:"742",height:"291"})}),"\n",(0,s.jsx)(n.p,{children:"Keep in mind your sketch polygon examples are randomly generated so your numbers will vary from the ones shown."}),"\n",(0,s.jsx)(n.p,{children:'And when viewing a sketch collection example, it should display the additional "Show By Sketch" list:'}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CoralReefCard collection view",src:t(97287).A+"",width:"748",height:"928"})}),"\n",(0,s.jsx)(n.h3,{id:"add-to-tab-report",children:"Add to Tab Report"}),"\n",(0,s.jsx)(n.p,{children:"Now add the CoralReefCard to a new page in your top-level TabReport."}),"\n",(0,s.jsxs)(n.p,{children:["Open ",(0,s.jsx)(n.code,{children:"src/clients/TabReport.tsx"})," and replace the code with the following:"]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/clients/TabReport.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-javascript",children:'import React, { useState } from "react";\nimport { useTranslation } from "react-i18next";\nimport {\n SegmentControl,\n ReportPage,\n SketchAttributesCard,\n} from "@seasketch/geoprocessing/client-ui";\nimport Translator from "../components/TranslatorAsync.js";\nimport { SimpleCard } from "../components/SimpleCard.js";\nimport { CoralReefCard } from "../components/CoralReefCard.js";\n\nconst BaseReport = () => {\n const { t } = useTranslation();\n const segments = [\n { id: "OVERVIEW", label: t("Overview") },\n { id: "BIOLOGICAL", label: t("Biological") },\n ];\n const [tab, setTab] = useState < string > "OVERVIEW";\n\n return (\n <>\n
\n setTab(segment)}\n segments={segments}\n />\n
\n \n \n \n \n \n \n \n >\n );\n};\n\n// Named export loaded by storybook\nexport const TabReport = () => {\n return (\n \n \n \n );\n};\n\n// Default export lazy-loaded by production ReportApp\nexport default TabReport;\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Storybook should update on save and display the following:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CoralReefCard add to page",src:t(62848).A+"",width:"1453",height:"823"})}),"\n",(0,s.jsx)(n.p,{children:"You should be able to click the tabs and switch between report pages."}),"\n",(0,s.jsx)(n.h2,{id:"benthic-habitat-report",children:"Benthic Habitat Report"}),"\n",(0,s.jsx)(n.p,{children:"Next you will create a report summarizing sketch overlap with 3 classes of rocky substrate (rock, rubble, sand) in the benthic zone (seabottom). Here is an image of it displayed in QGIS within the Micronesian EEZ boundary. Similar to the coral reefs, notice that these 3 types of rocky seabottom are mostly in shallower water near the islands and atolls."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Benthic habitat map",src:t(13065).A+"",width:"1345",height:"688"})}),"\n",(0,s.jsx)(n.h3,{id:"import-data-1",children:"Import Data"}),"\n",(0,s.jsx)(n.p,{children:"First, import the data."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run import:data\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"? Type of data?\nVector\n? Enter path to src file (with filename)\ndata/src/benthic-rock.fgb\n? Select layer to import\nbenthic-rock\n? Choose unique datasource name (a-z, A-Z, 0-9, -, _), defaults to filename\nbenthic-rock\n? Should multi-part geometries be split into single-part geometries?\nYes\n? (Optional) additional formats to create (besides fgb)\n[Press Enter to skip]\n? Select feature properties that you want to group metrics by\nclass\n? Select additional feature properties to keep in final datasource\n[Press Enter to skip]\n\nAdding benthic-rock record in project/datasources.json file\n"})}),"\n",(0,s.jsx)(n.h3,{id:"precalc-data-1",children:"Precalc Data"}),"\n",(0,s.jsxs)(n.p,{children:["Before you can use your benthic report, you need to precalculate the area of your benthic polygons. Rather than writing your own script for this, as was done for coral reefs, the ",(0,s.jsx)(n.code,{children:"precalc:data"})," command is available that will inspect your vector datasource and precalculate basic summary metrics (total feature area, total feature count, etc). Let's look at the datasource record generated for our benthic-rock datasource to understand what precalc will do."]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"project/datasources.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'{\n "src": "data/src/benthic-rock.fgb",\n "layerName": "benthic-rock",\n "geo_type": "vector",\n "datasourceId": "benthic-rock",\n "formats": [\n "fgb"\n ],\n "classKeys": [\n "class"\n ],\n "created": "2024-11-28T05:58:26.284Z",\n "lastUpdated": "2024-11-28T05:58:26.284Z",\n "propertiesToKeep": [\n "class"\n ],\n "explodeMulti": true,\n "precalc": true\n}\n'})})]}),"\n",(0,s.jsxs)(n.p,{children:["You'll notice that the ",(0,s.jsx)(n.code,{children:"precalc"})," property is set to true. That means that it is made available for precalculation. You can disable precalculation for any datasource you want at any time by setting it to ",(0,s.jsx)(n.code,{children:"false"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["You'll also notice that the ",(0,s.jsx)(n.code,{children:"class"})," attribute is configured under ",(0,s.jsx)(n.code,{children:"classKeys"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'"classKeys": [\n "class"\n],\n'})}),"\n",(0,s.jsxs)(n.p,{children:["This is because when importing your datasource, when asked to select feature properties that you want to group metrics by, you selected ",(0,s.jsx)(n.code,{children:"class"}),". If present, the precalc command will use this to precalculate metrics by each unique value present in the dataset for the ",(0,s.jsx)(n.code,{children:"class"})," attribute. If not present, you can simply add it now and save your file."]}),"\n",(0,s.jsx)(n.p,{children:"You're now ready to precalculate your metrics."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run precalc:data\n\n? Do you want to precalculate only a subset?\nNo, just precalculate everything (may take a while)\n\n...\n\n2 datasource/geography combinations precalculated successfully\n2 datasource/geography combinations skipped due to precalc disabled\n"})}),"\n",(0,s.jsxs)(n.p,{children:["You should now have precalculated ",(0,s.jsx)(n.code,{children:"area"})," and ",(0,s.jsx)(n.code,{children:"count"})," metrics for both reefextent and benthic-rock datasources. Let's look closer at the output."]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"project/precalc.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'[\n {\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Rock",\n "sketchId": null,\n "groupId": null,\n "value": 16604057.106034255\n },\n {\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Rubble",\n "sketchId": null,\n "groupId": null,\n "value": 14568314.003883593\n },\n {\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Sand",\n "sketchId": null,\n "groupId": null,\n "value": 41378302.21403051\n },\n {\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-total",\n "sketchId": null,\n "groupId": null,\n "value": 72550673.32394843\n },\n {\n "geographyId": "world",\n "metricId": "area",\n "classId": "reefextent-total",\n "sketchId": null,\n "groupId": null,\n "value": 716231422.607066\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "benthic-rock-Rock",\n "sketchId": null,\n "groupId": null,\n "value": 2712\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "benthic-rock-Rubble",\n "sketchId": null,\n "groupId": null,\n "value": 2002\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "benthic-rock-Sand",\n "sketchId": null,\n "groupId": null,\n "value": 2658\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "benthic-rock-total",\n "sketchId": null,\n "groupId": null,\n "value": 7372\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "reefextent-total",\n "sketchId": null,\n "groupId": null,\n "value": 14406\n }\n]\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Within this array of precalc metric records you will see four that represent the total area of all benthic-rock polygons and the total area for each of the 3 benthic rock classes:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-total",\n "sketchId": null,\n "groupId": null,\n "value": 72550673.32394843\n},\n{\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Rock",\n "sketchId": null,\n "groupId": null,\n "value": 16604057.106034255\n},\n{\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Rubble",\n "sketchId": null,\n "groupId": null,\n "value": 14568314.003883593\n},\n{\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Sand",\n "sketchId": null,\n "groupId": null,\n "value": 41378302.21403051\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"These will get loaded and used by the BenthicReefCard as the denominator value when calculating percent sketch overlap."}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["If at any point the process of using ",(0,s.jsx)(n.code,{children:"import:data"}),", ",(0,s.jsx)(n.code,{children:"precalc:data"})," or the ",(0,s.jsx)(n.code,{children:"projectClient"})," don't meet your needs, you are welcome to create your own separate workflow. As long as datasources get to the ",(0,s.jsx)(n.code,{children:"data/dist"})," directory for publishing, in the format (fgb, cog) and projection required (EPSG 4326 for vector, EPSG 6933 for raster) you can create your own solution."]})}),"\n",(0,s.jsx)(n.h3,{id:"world-geography",children:"World Geography"}),"\n",(0,s.jsxs)(n.p,{children:["You might have noticed in the precalculated metrics that they are assigned a geographyId of ",(0,s.jsx)(n.code,{children:"world"}),". ",(0,s.jsx)(n.code,{children:"Geographies"})," are a higher level feature of the framework that define polygon boundaries that serve a specfic purpose in your project. The main use case is to define planning boundaries for your project, if you have them."]}),"\n",(0,s.jsxs)(n.p,{children:["The default Geography for a new project is the ",(0,s.jsx)(n.code,{children:"world"})," geography, which establishes the entire world as your planning boundary. This is sufficient for your needs until you have a more specific planning boundary that you want to work with. For example you can clip your sketches and your data to a geography in order to report metrics for a specific geography. Since your data is already pre-clipped to the planning area, and there is only one planning area, you don't need to do anything more with this feature. You can just leave it to use the ",(0,s.jsx)(n.code,{children:"world"})," geography."]}),"\n",(0,s.jsxs)(n.p,{children:["Geographies are defined in ",(0,s.jsx)(n.code,{children:"project/geographies.json"}),". To learn more visit the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/concepts#geographies",children:"advanced concepts"})," page."]}),"\n",(0,s.jsx)(n.h3,{id:"add-metric-group",children:"Add Metric Group"}),"\n",(0,s.jsxs)(n.p,{children:["A metric group is a higher-level entity that defines a metric to be measured, for one or more classes of data. ",(0,s.jsx)(n.code,{children:"MetricGroup"})," ",(0,s.jsx)(n.strong,{children:"records"})," can defined in ",(0,s.jsx)(n.code,{children:"project/metrics.json"})," and accessed using the project client in your geoprocessing functions and reports."]}),"\n",(0,s.jsxs)(n.p,{children:["Let's create a metric group by first looking at the benthic dataset. It represents where multiple classes of benthic habitat are present - sand, rock, rubble. Each polygon is assigned with a single habitat type using the ",(0,s.jsx)(n.code,{children:"class"})," attribute and given a value of ",(0,s.jsx)(n.code,{children:"Sand"}),", ",(0,s.jsx)(n.code,{children:"Rock"}),", or ",(0,s.jsx)(n.code,{children:"Rubble"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Add the following metric group object to ",(0,s.jsx)(n.code,{children:"project/metrics.json"})," and save the file."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "metricId": "benthicHabitat",\n "classKey": "class",\n "datasourceId": "benthic-rock",\n "classes": [\n {\n "classId": "Sand",\n "display": "Sand"\n },\n {\n "classId": "Rock",\n "display": "Rock"\n },\n {\n "classId": "Rubble",\n "display": "Rubble"\n }\n ]\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["This defines a ",(0,s.jsx)(n.code,{children:"benthicHabitat"})," metric that sources data from the ",(0,s.jsx)(n.code,{children:"benthic"})," datasource. The ",(0,s.jsx)(n.code,{children:"classKey"})," indicates this datasource has an attribute named ",(0,s.jsx)(n.code,{children:"class"})," used to identify which data class each polygon is a member of. 3 data classes are defined with a ",(0,s.jsx)(n.code,{children:"classId"})," serving as the unique identifier for the data class, and it also matches the value used in the data at the ",(0,s.jsx)(n.code,{children:"classKey"})," attribute."]}),"\n",(0,s.jsxs)(n.p,{children:["To learn more about metric groups, visit the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/concepts#metric-group",children:"advanced concepts"})," page."]}),"\n",(0,s.jsx)(n.h3,{id:"create-report-1",children:"Create Report"}),"\n",(0,s.jsx)(n.p,{children:"Next you will create a report that uses your metric group. Run the following command and answer the questions:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run create:report\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"? Type of report to create\nVector overlap report - calculates sketch overlap with vector datasources\n? Describe what this reports geoprocessing function will calculate (e.g. Calculate sketch overlap with boundary polygons)\nCalculate sketch overlap with benthic habitat\n? Select the metric group to report on\nbenthicHabitat\n\n\u2714 Created benthicHabitat report\n\u2714 Registered report assets in project/geoprocessing.json\n\nGeoprocessing function: src/functions/benthicHabitat.ts\nSmoke test: src/functions/benthicHabitatSmoke.test.ts\nReport component: src/components/BenthicHabitatCard.tsx\nStory generator: src/components/BenthicHabitatCard.example-stories.ts\n\nNext Steps:\n * 'npm test' to run smoke tests against your new geoprocessing function\n * 'npm run storybook' to view your new report with smoke test output\n * Add to a top-level report client or page when ready\n"})}),"\n",(0,s.jsx)(n.p,{children:"You should now have a geoprocessing function and card component ready to go that will iterate through your data classes and calculate/report area overlap with your sketch."}),"\n",(0,s.jsx)(n.h3,{id:"test-new-example-sketch",children:"Test New Example Sketch"}),"\n",(0,s.jsxs)(n.p,{children:["Now run ",(0,s.jsx)(n.code,{children:"npm run test"})," again look at the new smoke test output for your geoprocessing function in ",(0,s.jsx)(n.code,{children:"examples/output"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"It's very likely that none of your random sketchs overlapped with any benthic polygons and all display zero. Add the following example sketch that we know will overlap."}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"examples/sketches/sketch2.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "type": "Feature",\n "properties": {\n "id": "78f6e916-20f0-471e-a15e-6d632650cf68",\n "isCollection": false,\n "userAttributes": [\n {\n "label": "Type",\n "fieldType": "ChoiceField",\n "exportId": "TYPE",\n "value": "sketch"\n },\n {\n "label": "Notes",\n "value": "NOTES",\n "fieldType": "TextArea"\n }\n ],\n "sketchClassId": "3ac026ad-c3eb-471a-b6ad-58782aa5e949",\n "createdAt": "2024-11-26T02:48:33.985Z",\n "updatedAt": "2024-11-26T02:48:33.985Z",\n "name": "sketch2"\n },\n "geometry": {\n "type": "Polygon",\n "coordinates": [\n [\n [151.31665625673213, 7.749571426060996],\n [151.31665625673213, 5.925462431466443],\n [153.9861009666032, 5.925462431466443],\n [153.9861009666032, 7.749571426060996],\n [151.31665625673213, 7.749571426060996]\n ]\n ]\n },\n "id": "78f6e916-20f0-471e-a15e-6d632650cf68"\n}\n'})})]}),"\n",(0,s.jsxs)(n.p,{children:["Now ",(0,s.jsx)(n.code,{children:"npm run test"})," and you should now see non-zero output for each benthic class for the sketch2 example:"]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"examples/output/sketch2/benthicHabitat.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "metrics": [\n {\n "geographyId": "world",\n "metricId": "benthicHabitat",\n "classId": "Rock",\n "sketchId": "78f6e916-20f0-471e-a15e-6d632650cf68",\n "groupId": null,\n "value": 11210186.968081,\n "extra": {\n "sketchName": "sketch2"\n }\n },\n {\n "geographyId": "world",\n "metricId": "benthicHabitat",\n "classId": "Rubble",\n "sketchId": "78f6e916-20f0-471e-a15e-6d632650cf68",\n "groupId": null,\n "value": 11210186.968081,\n "extra": {\n "sketchName": "sketch2"\n }\n },\n {\n "geographyId": "world",\n "metricId": "benthicHabitat",\n "classId": "Sand",\n "sketchId": "78f6e916-20f0-471e-a15e-6d632650cf68",\n "groupId": null,\n "value": 11210186.968081,\n "extra": {\n "sketchName": "sketch2"\n }\n }\n ]\n}\n'})})]}),"\n",(0,s.jsx)(n.h3,{id:"add-to-tab-report-1",children:"Add To Tab Report"}),"\n",(0,s.jsxs)(n.p,{children:["Next, add BenthicHabitatCard to a new ",(0,s.jsx)(n.strong,{children:"Habitat"})," page in TabReport. Open ",(0,s.jsx)(n.code,{children:"src/clients/TabReport.tsx"})," and replace the code with the following:"]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/clients/TabReport.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-javascript",children:'import React, { useState } from "react";\nimport { useTranslation } from "react-i18next";\nimport {\n SegmentControl,\n ReportPage,\n SketchAttributesCard,\n} from "@seasketch/geoprocessing/client-ui";\nimport Translator from "../components/TranslatorAsync.js";\nimport { SimpleCard } from "../components/SimpleCard.js";\nimport { CoralReefCard } from "../components/CoralReefCard.js";\nimport { BenthicHabitatCard } from "../components/BenthicHabitatCard.js";\n\nconst BaseReport = () => {\n const { t } = useTranslation();\n const segments = [\n { id: "OVERVIEW", label: t("Overview") },\n { id: "BIOLOGICAL", label: t("Biological") },\n { id: "HABITAT", label: t("Habitat") },\n ];\n const [tab, setTab] = useState < string > "OVERVIEW";\n\n return (\n <>\n
\n setTab(segment)}\n segments={segments}\n />\n
\n \n \n \n \n \n \n \n \n \n \n >\n );\n};\n\n// Named export loaded by storybook\nexport const TabReport = () => {\n return (\n \n \n \n );\n};\n\n// Default export lazy-loaded by production ReportApp\nexport default TabReport;\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Storybook should update on save and display the following:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CoralReefCard add to page",src:t(2187).A+"",width:"1464",height:"1299"})}),"\n",(0,s.jsx)(n.h2,{id:"seamount-report",children:"Seamount Report"}),"\n",(0,s.jsx)(n.p,{children:"Next you will create a report summarizing sketch overlap with areas that are within 40k kilometers of a seamount, which is an underwater mountain that rises at least 1,000 meters above the surrounding ocean. Here is an image of these areas displayed in QGIS within the Micronesian EEZ boundary."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Seamount",src:t(37720).A+"",width:"1080",height:"607"})}),"\n",(0,s.jsx)(n.p,{children:"The seamount dataset is in a raster format. It is a binary raster such that each raster cell has a value of zero or one. Rasters are like digital images, in that each pixel or cell represents a specific rectangular area of the world and gives it a value. This particular dataset is a binary raster. Each cell has a value of zero or one. A one value indicates that the cell is within 40 kilemeters of a seamount, a zero value indicates it is not."}),"\n",(0,s.jsx)(n.h3,{id:"import-data-2",children:"Import Data"}),"\n",(0,s.jsx)(n.p,{children:"Next you will import this seamount raster:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run import:data\n\n? Type of data?\nRaster\n\n? Enter path to src file (with filename)\ndata/src/seamounts_40km.tif\n\n? Choose unique datasource name (a-z, A-Z, 0-9, -, _), defaults to filename\nseamounts_40km\n\n? Select raster band to import\n1\n\n? What type of measurement is used for this raster data?\nQuantitative - cell value (number) represents a measurement of a single thing\n\nAdding seamounts_40km record in project/datasources.json file\n"})}),"\n",(0,s.jsx)(n.h3,{id:"precalc-data-2",children:"Precalc Data"}),"\n",(0,s.jsxs)(n.p,{children:["Now, precalculating metrics for a cloud-optimized geotiff raster is a little more complicated than for a flatgeobuf. For this reason, we want to make use of the built-in ",(0,s.jsx)(n.code,{children:"precalc"})," feature. Run the precalc command as follow:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run precalc:data\n\n? Do you want to precalculate only a subset?\nYes, by datasource\n\n? Which datasources do you want to precalculate? (will precalculate for all geographies)\nLet me choose\n\n? What datasources would you like to precalculate? (select as many as you want)\nseamounts_40km - raster\n\nPrecalculating datasource seamounts_40km for geography world\n1 datasource/geography combinations precalculated successfully\n"})}),"\n",(0,s.jsx)(n.p,{children:"Now look at project/precalc.json. You should see 4 new precalculated metrics for octocorals:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"valid"})," - count of all raster cells with value (not nodata cells)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"count"})," - count of all cells in the raster, both valid and invalid (nodata)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"sum"})," - sum of value of all valid cell values in raster"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"area"})," - area of valid cells in raster in square meters"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:' {\n "geographyId": "world",\n "metricId": "area",\n "classId": "seamounts_40km-total",\n "sketchId": null,\n "groupId": "band-0",\n "value": 400949272332.4638\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "seamounts_40km-total",\n "sketchId": null,\n "groupId": "band-0",\n "value": 18748\n },\n {\n "geographyId": "world",\n "metricId": "sum",\n "classId": "seamounts_40km-total",\n "sketchId": null,\n "groupId": "band-0",\n "value": 1365\n },\n {\n "geographyId": "world",\n "metricId": "valid",\n "classId": "seamounts_40km-total",\n "sketchId": null,\n "groupId": "band-0",\n "value": 1365\n }\n'})}),"\n",(0,s.jsx)(n.p,{children:"The area calculation is made possible by the fact that the raster is in an equal area projection, making all raster cells a consistent size. Area is calculated as:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"area = raster cell width in meters x cell height in meters x number of valid cells"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Notice that the precalculated ",(0,s.jsx)(n.code,{children:"sum"})," and ",(0,s.jsx)(n.code,{children:"valid"})," values are the same at ",(0,s.jsx)(n.code,{children:"1365"}),". That is because the valid cells all have a value of 1 and the sum of the values in valid cells is the same as the count of valid cells."]}),"\n",(0,s.jsx)(n.h3,{id:"add-objective",children:"Add Objective"}),"\n",(0,s.jsxs)(n.p,{children:["You will also use the built-in framework support for objectives. It allows you to configure a target value and measure progress toward it in a report. Open ",(0,s.jsx)(n.code,{children:"project/objectives.json"})," and add the following objective:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'[\n {\n "objectiveId": "seamounts",\n "shortDesc": "Seamounts 30%",\n "target": 0.3,\n "countsToward": {}\n }\n]\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"countsToward"})," property isn't necessary for this sample project but it allows you to indicate which of one or more categories count towards meeting the target. For example if you allow a user to assign a protection level to their sketch, you can allow only the two highest levels of protection to count toward meeting the target."]}),"\n",(0,s.jsx)(n.p,{children:"Example (do not add):"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "countsToward": {\n "Full Protection": "yes",\n "High Protection": "yes",\n "Low Protection": "no"\n }\n}\n'})}),"\n",(0,s.jsx)(n.h3,{id:"add-metric-group-1",children:"Add Metric Group"}),"\n",(0,s.jsx)(n.p,{children:"The last bit of preparation is you will create a metric group. This will allow you to easily access your precalc metrics and your objective in your report card."}),"\n",(0,s.jsxs)(n.p,{children:["Create a seamount metric group that uses the objective in ",(0,s.jsx)(n.code,{children:"project/metrics.json"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "metricId": "seamounts",\n "datasourceId": "seamounts_40km",\n "classes": [\n {\n "classId": "seamounts",\n "display": "Seamounts",\n "objectiveId": "seamounts"\n }\n ]\n}\n'})}),"\n",(0,s.jsx)(n.h3,{id:"create-report-2",children:"Create Report"}),"\n",(0,s.jsx)(n.p,{children:"Now create a blank seamount report"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"npm run create:report\n\n? Type of report to create\nBlank report - empty report ready to build from scratch\n\n? Describe what this reports geoprocessing function will calculate (e.g. Calculate sketch overlap with boundary polygons)\nCalculate sketch overlap with seamount raster\n\n? Title for this report, in camelCase\nseamounts\n\n\u2714 Created seamounts report\n\u2714 Registered report assets in project/geoprocessing.json\n\nGeoprocessing function: src/functions/seamounts.ts\nSmoke test: src/functions/seamountsSmoke.test.ts\nReport component: src/components/SeamountsCard.tsx\nStory generator: src/components/SeamountsCard.example-stories.ts\n\nNext Steps:\n * 'npm test' to run smoke tests against your new geoprocessing function\n * 'npm run storybook' to view your new report with smoke test output\n * Add to a top-level report client or page when ready\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This creates both a geoprocessing function and report card, and registers them in ",(0,s.jsx)(n.code,{children:"project/geoprocessing.json"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Open ",(0,s.jsx)(n.code,{children:"src/functions/seamounts.ts"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"You will now update this code answer the following questions:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"What percentage of area within 40 kilometers of a seamount is within the current sketch polygon (or sketch collection polygons)?"}),"\n",(0,s.jsx)(n.li,{children:"If it is a sketch collection, does it meet the planning objective of protecting 30% of all area within 40 kilometers of a seamount?"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Replace the existing code with the following:"}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/functions/seamounts.ts"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'import {\n Sketch,\n SketchCollection,\n Polygon,\n MultiPolygon,\n GeoprocessingHandler,\n DefaultExtraParams,\n loadCog,\n rasterStats,\n toSketchArray,\n StatsObject,\n} from "@seasketch/geoprocessing";\nimport project from "../../project/projectClient.js";\n\nexport interface SeamountResult {\n /** Sum of valid seamount raster cells overlapping with sketch */\n stats: StatsObject[];\n childSketchStats: {\n /** Name of the sketch */\n name: string;\n /** Sum of valid seamount raster cells overlapping with sketch */\n stats: StatsObject[];\n }[];\n}\n\n/**\n * seamounts for use with create:report command\n */\nexport async function seamounts(\n sketch:\n | Sketch\n | SketchCollection,\n extraParams: DefaultExtraParams = {},\n): Promise {\n const metricGroup = project.getMetricGroup("seamounts");\n const ds = project.getMetricGroupDatasource(metricGroup);\n const url = project.getDatasourceUrl(ds);\n const raster = await loadCog(url);\n\n // Add analysis code\n const stats = await rasterStats(raster, {\n feature: sketch,\n stats: ["sum"],\n });\n\n let childSketchStats: SeamountResult["childSketchStats"] = [];\n if (sketch.properties.isCollection) {\n childSketchStats = await Promise.all(\n toSketchArray(sketch).map(async (childSketch) => {\n const childStats = await rasterStats(raster, {\n feature: childSketch,\n stats: ["sum"],\n });\n return {\n name: childSketch.properties.name,\n stats: childStats,\n };\n }),\n );\n }\n\n // Custom return type\n return {\n stats,\n childSketchStats,\n };\n}\n\nexport default new GeoprocessingHandler(seamounts, {\n title: "seamounts",\n description: "Calculate sketch overlap with seamount data",\n timeout: 60, // seconds\n memory: 1024, // megabytes\n executionMode: "async",\n});\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Notice the more sophisticated result payload. It's designed to return one or more raster stats for the top-level sketch, and one or more child sketch stats if it's a sketch collection. This gives the structure some room to grow if you want to produce multiple raster stats for each sketch and use them in this report."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"export interface SeamountResult {\n /** Sum of valid seamount raster cells overlapping with sketch */\n stats: StatsObject[];\n childSketchStats: {\n /** Name of the sketch */\n name: string;\n /** Sum of valid seamount raster cells overlapping with sketch */\n stats: StatsObject[];\n }[];\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Then it fetches the metadata for the seamounts raster, ready to read data from it."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'const metricGroup = project.getMetricGroup("seamounts");\nconst ds = project.getMetricGroupDatasource(metricGroup);\nconst url = project.getDatasourceUrl(ds);\nconst raster = await loadCog(url);\n'})}),"\n",(0,s.jsx)(n.p,{children:"Now let's look at the analysis code. If it's a sketch collection, sum the value of all rasters cells that overlap with each child sketch."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// If sketch is collection, sum the value of raster cells that overlap with each child sketch\nlet childSketchStats: SeamountResult["childSketchStats"] = [];\nif (sketch.properties.isCollection) {\n childSketchStats = await Promise.all(\n toSketchArray(sketch).map(async (childSketch) => {\n const childStats = await rasterStats(raster, {\n feature: childSketch,\n stats: ["sum"],\n });\n return {\n name: childSketch.properties.name,\n stats: childStats,\n };\n }),\n );\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Next, sum the value of raster cells that overlap the entire top-level sketch or sketch collection and return the final result payload."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// Calculate overall sketch area\nconst stats = await rasterStats(raster, {\n feature: sketch,\n stats: ["sum"],\n});\n\n// Custom return type\nreturn {\n stats,\n childSketchStats,\n};\n'})}),"\n",(0,s.jsx)(n.p,{children:"If this is a sketch collection, you might notice that an optimization would be to sum the value of all the child sketches to get the overall sum for the whole collection. That is true, if your sketches are guaranteed not to overlap. In practice, sketches often can and do overlap in a planning process. The planning process may even allow it such as areas of higher protection within areas of lower protection. This is an optimization left to you."}),"\n",(0,s.jsx)(n.p,{children:"Now run tests"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm test\n"})}),"\n",(0,s.jsx)(n.p,{children:"Confirm that the output looks as expected."}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"examples/output/sketchCollection1/seamounts.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:'{\n "stats": [\n {\n "sum": 5\n }\n ],\n "childSketchStats": [\n {\n "name": "sketchCollection1-1",\n "stats": [\n {\n "sum": 1\n }\n ]\n },\n {\n "name": "sketchCollection1-2",\n "stats": [\n {\n "sum": 1\n }\n ]\n },\n {\n "name": "sketchCollection1-3",\n "stats": [\n {\n "sum": 0\n }\n ]\n },\n {\n "name": "sketchCollection1-4",\n "stats": [\n {\n "sum": 0\n }\n ]\n },\n {\n "name": "sketchCollection1-5",\n "stats": [\n {\n "sum": 1\n }\n ]\n },\n {\n "name": "sketchCollection1-6",\n "stats": [\n {\n "sum": 0\n }\n ]\n },\n {\n "name": "sketchCollection1-7",\n "stats": [\n {\n "sum": 2\n }\n ]\n },\n {\n "name": "sketchCollection1-8",\n "stats": [\n {\n "sum": 0\n }\n ]\n },\n {\n "name": "sketchCollection1-9",\n "stats": [\n {\n "sum": 0\n }\n ]\n },\n {\n "name": "sketchCollection1-10",\n "stats": [\n {\n "sum": 0\n }\n ]\n }\n ]\n}\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Now, open src/components/SeamountsCard.tsx."}),"\n",(0,s.jsx)(n.p,{children:"You will now update this code to:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Display the % of total area within 40 kilometers of a seamount captured within this sketch"}),"\n",(0,s.jsxs)(n.li,{children:["If it is a sketch collection","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Indicate whether the objective of protecting 30% of all area within 40 kilometers of a seamount has been met."}),"\n",(0,s.jsx)(n.li,{children:"Display a collapsible area with a breakdown of the area and % area of seamount within each individual sketch in the collection."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Replace the existing code with the following:"}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/components/SeamountCard.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-javascript",children:'import React from "react";\nimport { Trans, useTranslation } from "react-i18next";\nimport {\n Collapse,\n HorizontalStackedBar,\n ObjectiveStatus,\n ResultsCard,\n Table,\n useSketchProperties,\n VerticalSpacer,\n} from "@seasketch/geoprocessing/client-ui";\nimport {\n percentWithEdge,\n roundDecimalFormat,\n squareMeterToKilometer,\n} from "@seasketch/geoprocessing/client-core";\nimport project from "../../project/projectClient.js";\nimport { SeamountResult } from "../functions/seamounts.js";\n\nexport const SeamountsCard = () => {\n const { t } = useTranslation();\n const [{ isCollection }] = useSketchProperties();\n const titleTrans = t("SeamountCard title", "SeamountCard");\n\n // Get precalc total sum\n const curGeography = project.getGeographyById("world", {\n fallbackGroup: "default-boundary",\n });\n const metricGroup = project.getMetricGroup("seamounts", t);\n const precalcMetrics = project.getPrecalcMetrics(\n metricGroup,\n "sum",\n curGeography.geographyId,\n );\n const sumTotal = precalcMetrics[0].value;\n\n // Get objective target\n const target = project.getObjectiveById("seamounts").target;\n\n return (\n <>\n \n {(data: SeamountResult) => {\n console.log("precalc", precalcMetrics);\n console.log("data", data);\n\n const sumPerc = data.stats[0].sum! / sumTotal;\n const sumPercString = percentWithEdge(sumPerc);\n const targetPercString = percentWithEdge(target);\n\n const meetsObjective = sumPerc >= target;\n\n // Adjust values for chart to be in range 0-100\n const chartRows = [[[sumPerc * 100]]];\n\n const sketchStr = isCollection ? t("sketch collection") : t("sketch");\n\n const meetsOrNotElement = meetsObjective ? (\n \n This {{ sketchStr }} meets the objective of protecting{" "}\n {{ targetPercString }} of area within 40 km of a seamount.\n \n ) : (\n \n This {{ sketchStr }} does not meet the objective of protecting{" "}\n {{ targetPercString }} of area within 40 km of a seamount.\n \n );\n\n return (\n <>\n
\n \n {{ sumPercString }} of all areas within 40 kilometers of a\n seamount is within this {{ sketchStr }}.\n \n
\n roundDecimalFormat(\n squareMeterToKilometer(row.stats[0].sum),\n ),\n },\n {\n Header: t("% Seamount within Sketch"),\n accessor: (row: any) =>\n percentWithEdge(row.stats[0].sum / sumTotal),\n },\n ]}\n />\n \n )}\n >\n );\n }}\n \n >\n );\n};\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"There are multiple things worth noticing:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"the project client is getting more use, to access precalc metrics and the objective target."}),"\n",(0,s.jsx)(n.li,{children:"the code to access the result values is more complex than for the reef report, because the structure of the result data is more complex."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"add-to-tab-report-2",children:"Add To Tab Report"}),"\n",(0,s.jsxs)(n.p,{children:["Next, add SeamountsCard to the ",(0,s.jsx)(n.strong,{children:"Habitat"})," page in TabReport. Open ",(0,s.jsx)(n.code,{children:"src/clients/TabReport.tsx"})," and insert the following at the appropriate places:"]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/clients/TabReport.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:'import { SeamountsCard } from "../components/SeamountsCard.js";\n\n\n \n \n;\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Storybook should update on save and display the following:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Seamount add to page",src:t(38223).A+"",width:"1474",height:"1126"})}),"\n",(0,s.jsx)(n.h3,{id:"data-complexity",children:"Data Complexity"}),"\n",(0,s.jsx)(n.p,{children:"Results can get even more complex than the last report. Imagine if a report needed to calculate a metric with 10 different classes of data. And each sketch can be assigned to 1 of 4 different protection levels. The planning process is also split out into 3 different subregions. Now imagine you need to calculate metrics for every combination of data class, protection level, and subregion. How would you design the structure of your result to accommodate the data? How would you do it in a way that is flexible and reusable so that components of the framework can build on it?"}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"Metric"})," data type is designed to accommodate this type of multi-dimensional data. You see a glimpse of it in the precalc output, and in the Coral Reef report. Each ",(0,s.jsx)(n.code,{children:"Metric"})," object represents a single measurement/value for one or more dimensions of data. A simple array of these Metric objects can represent your entire result payload."]}),"\n",(0,s.jsxs)(n.p,{children:["Example of a single Metric ",(0,s.jsx)(n.code,{children:"coralspecies"}),", that measures a sketches overlap with data class ",(0,s.jsx)(n.code,{children:"blackcoral"}),", in ",(0,s.jsx)(n.code,{children:"subregion 1"}),", where the sketch is assigned ",(0,s.jsx)(n.code,{children:"full protection"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "geographyId": "subregion1",\n "metricId": "coralspecies",\n "classId": "blackcoral",\n "sketchId": "78f6e916-20f0-471e-a15e-6d632650cf68",\n "groupId": "full_protection",\n "value": 3,\n "extra": {\n "sketchName": "sketch2"\n }\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Multiple pieces of this framework know how to work with ",(0,s.jsx)(n.code,{children:"Metrics"})," including:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"precalc:data"})}),"\n",(0,s.jsxs)(n.li,{children:["high-level spatial analysis functions like ",(0,s.jsx)(n.code,{children:"rasterMetrics"})," and ",(0,s.jsx)(n.code,{children:"overlapFeatures"})]}),"\n",(0,s.jsxs)(n.li,{children:["UI components like ",(0,s.jsx)(n.code,{children:"ClassTable"})," and ",(0,s.jsx)(n.code,{children:"SketchClassTable"})]}),"\n",(0,s.jsxs)(n.li,{children:["helper functions like ",(0,s.jsx)(n.code,{children:"firstMatchingMetric"}),", ",(0,s.jsx)(n.code,{children:"toPercentMetric"})," and ",(0,s.jsx)(n.code,{children:"sortMetrics"})]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"coral-species-report",children:"Coral Species Report"}),"\n",(0,s.jsx)(n.p,{children:"This last report will calculate sketch overlap with 3 difference species of coral."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"3 corals",src:t(88318).A+"",width:"1126",height:"600"})}),"\n",(0,s.jsx)(n.h3,{id:"import-data-3",children:"Import Data"}),"\n",(0,s.jsx)(n.p,{children:"First, we'll import the datasets. There are three binary rasters, each has cells with a value of zero or one. Where there is a one value, the species is predicted to be present."}),"\n",(0,s.jsx)(n.p,{children:"Import the datasets as follow:"}),"\n",(0,s.jsx)(n.p,{children:"Black Coral:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run import:data\n\n? Type of data?\nRaster\n\n? Enter path to src file (with filename)\ndata/src/blackcoral.tif\n\n? Choose unique datasource name (a-z, A-Z, 0-9, -, _), defaults to filename\nblackcoral\n\n? Select raster band to import\n1\n\n? What type of measurement is used for this raster data?\nQuantitative - cell value (number) represents a measurement of a single thing\n"})}),"\n",(0,s.jsx)(n.p,{children:"Cold Water Coral:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run import:data\n\n? Type of data?\nRaster\n\n? Enter path to src file (with filename)\ndata/src/coldwatercoral.tif\n\n? Choose unique datasource name (a-z, A-Z, 0-9, -, _), defaults to filename\ncoldwatercoral\n\n? Select raster band to import\n1\n\n? What type of measurement is used for this raster data?\nQuantitative - cell value (number) represents a measurement of a single thing\n"})}),"\n",(0,s.jsx)(n.p,{children:"Octocoral:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run import:data\n\n? Type of data?\nRaster\n\n? Enter path to src file (with filename)\ndata/src/octocoral.tif\n\n? Choose unique datasource name (a-z, A-Z, 0-9, -, _), defaults to filename\noctocoral\n\n? Select raster band to import\n1\n\n? What type of measurement is used for this raster data?\nQuantitative - cell value (number) represents a measurement of a single thing\n"})}),"\n",(0,s.jsx)(n.h3,{id:"precalc",children:"Precalc"}),"\n",(0,s.jsx)(n.p,{children:"Now precalculate metrics for the raster."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"npm run precalc:data\n\n? Do you want to precalculate only a subset?\nYes, by datasource\n\n? Which datasources do you want to precalculate? (will precalculate for all geographies)\nLet me choose\n\n? What datasources would you like to precalculate? (select as many as you want)\nblackcoral - raster\ncoldwatercoral - raster\noctocoral - raster\n\n3 datasource/geography combinations precalculated successfully\n"})}),"\n",(0,s.jsx)(n.h3,{id:"add-metric-group-2",children:"Add Metric Group"}),"\n",(0,s.jsxs)(n.p,{children:["Now define a metric group in ",(0,s.jsx)(n.code,{children:"project/metrics.json"})," consisting of three classes, one for each type of coral, each pointing to the appropriate datasource at the class level:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "metricId": "coralspecies",\n "classes": [\n {\n "datasourceId": "blackcoral",\n "classId": "blackcoral",\n "display": "Black Coral",\n "objectiveId": "blackcoral"\n },\n {\n "datasourceId": "coldwatercoral",\n "classId": "coldwatercoral",\n "display": "Cold Water Corals",\n "objectiveId": "coldwatercoral"\n },\n {\n "datasourceId": "octocoral",\n "classId": "Octocoral",\n "display": "Octocoral",\n "objectiveId": "octocoral"\n }\n ]\n}\n'})}),"\n",(0,s.jsx)(n.h3,{id:"add-objective-1",children:"Add Objective"}),"\n",(0,s.jsxs)(n.p,{children:["Open ",(0,s.jsx)(n.code,{children:"project/objectives.json"})," and add an objective for each data class:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:' {\n "objectiveId": "blackcoral",\n "shortDesc": "Black Coral 15%",\n "target": 0.15,\n "countsToward": {}\n },\n {\n "objectiveId": "coldwatercoral",\n "shortDesc": "Cold Water Coral 25%",\n "target": 0.25,\n "countsToward": {}\n },\n {\n "objectiveId": "octocoral",\n "shortDesc": "Octocoral 35%",\n "target": 0.35,\n "countsToward": {}\n }\n'})}),"\n",(0,s.jsx)(n.h3,{id:"create-report-3",children:"Create Report"}),"\n",(0,s.jsx)(n.p,{children:"Now create the report:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"npm run create:report\n\n? Type of report to create\nRaster overlap report - calculates sketch overlap with raster datasources\n\n? Describe what this reports geoprocessing function will calculate (e.g. Calculate sketch overlap with boundary polygons)\nCalculate sketch overlap with coral species\n\n? Select the metric group to report on\ncoralspecies\n\n? Type of raster data\nQuantitative - Continuous variable across the raster\n\n? Statistic to calculate\nsum - sum of value of valid cells overlapping with sketch\n\n\u2714 Created coralspecies report\n\u2714 Registered report assets in project/geoprocessing.json\n"})}),"\n",(0,s.jsx)(n.p,{children:"Then run smoke tests:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run test\n"})}),"\n",(0,s.jsx)(n.h3,{id:"add-to-tab-report-3",children:"Add To Tab Report"}),"\n",(0,s.jsxs)(n.p,{children:["Next, add CoralspeciesCard to the ",(0,s.jsx)(n.strong,{children:"Biological"})," page in TabReport. Open ",(0,s.jsx)(n.code,{children:"src/clients/TabReport.tsx"})," and insert the following at the appropriate places:"]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/clients/TabReport.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:'import { CoralspeciesCard } from "../components/CoralspeciesCard.js";\n\n\n \n \n;\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Storybook should update on save and display the following:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CoralReefCard add to page",src:t(16365).A+"",width:"1459",height:"1041"})}),"\n",(0,s.jsx)(n.h3,{id:"language-translation-1",children:"Language Translation"}),"\n",(0,s.jsx)(n.p,{children:"To support more languages than just English, start with extracting all of the latest strings from your reports:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run extract:translation\n"})}),"\n",(0,s.jsxs)(n.p,{children:["You will want to then look at the git changes produced in ",(0,s.jsx)(n.code,{children:"src/i18n/lang/en/translation.json"})," and make adjustments to your reports until the extract strings look right."]}),"\n",(0,s.jsx)(n.p,{children:"To learn more visit the\n[LINK TO TRANSLATION DOC]"}),"\n",(0,s.jsx)(n.h2,{id:"create-github-project",children:"Create Github Project"}),"\n",(0,s.jsxs)(n.p,{children:["At this point, you can push your code from your local git repository to a remote repository on Github. First, ",(0,s.jsx)(n.a,{href:"https://github.com/new",children:"create a remote Github repository"})," called ",(0,s.jsx)(n.code,{children:"fsm-reports-test"}),". Leave it empty, do not choose to initialize with a template, README, gitignore, or LICENSE."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"git remote add origin https://github.com/PUT_YOUR_GITHUB_ORG_OR_USERNAME_HERE/fsm-reports-test.git\ngit push -u origin main\n"})}),"\n",(0,s.jsx)(n.p,{children:"You should see your files successfuly pushed to Github."}),"\n",(0,s.jsx)(n.p,{children:"It may ask you if it can use the Github extension to sign you in using Github. It will open a browser tab and communicate with the Github website. If you are already logged in there, then it should be done quickly, otherwise it may have you login to Github."}),"\n",(0,s.jsx)(n.h2,{id:"whats-next",children:"What's Next"}),"\n",(0,s.jsx)(n.p,{children:"You've now completed the sample tutorial. Next steps include:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Further customize these reports to suit your needs. Look at the storybook ",(0,s.jsx)(n.a,{href:"/storybook",children:"component libary"})," to see what is available."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/tutorials/newproject",children:"Create a new project"}),", deploy it to a production environment, publish your data, and integrate your reports with SeaSketch."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/tutorials/existingproject",children:"Setup an existing project"}),", and re-deploy it."]}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},16365:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/3-coral-species-add-page-da3d51726173469ff243233b7618ef3a.jpg"},88318:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/3-corals-map-07fe79be9d94f5eb8383930271dbed3c.jpg"},13065:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/benthic-habitat-map-0fb7d81be6181cf1ae11e0ce7db0a9f9.jpg"},2187:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/benthic-habitats-add-page-821de9a1cb7c036d59d6defc10f23460.jpg"},62848:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/coral-reef-card-add-to-page-89529c6834ef771d4a06d72ac0137b0f.jpg"},97287:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/coral-reef-card-collection-42587097890cc8cb14f0276d105af2b1.jpg"},8847:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/coral-reef-card-sketch-a752ef560d2ef81510dcc024f968b2b3.jpg"},81465:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/eez-bbox-eaed173f82fe29cdd6c06782e621ff43.jpg"},94616:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/eez-with-land-a17837dd0ade1b26dd289529dc938a38.jpg"},41538:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/preprocessing-after-e56cf27fb55e189a0e64f9f57b9bf08c.jpg"},58667:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/preprocessing-before-9d8ee3df0ac13b3e9f47dec8a2dc8d11.jpg"},35162:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/reef-extent-c5b22beda714f8f21941e25144be67f2.jpg"},38223:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/seamount-add-page-cab8cc26896f4fba2d2bb5879d19a6c7.jpg"},37720:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/seamount-map-dc1c4407048d2b89867320ad14dcf8b7.jpg"},51899:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/simple-card-table-fb0438e81de84b2fabe0ab3a176cb0c5.jpg"},44424:(e,n,t)=>{t.d(n,{A:()=>s});const s=t.p+"assets/images/simple-card-view-baa6031f291f646309b58f5edcfe6c9a.jpg"},28453:(e,n,t)=>{t.d(n,{R:()=>o,x:()=>i});var s=t(96540);const r={},a=s.createContext(r);function o(e){const n=s.useContext(a);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),s.createElement(a.Provider,{value:n},e.children)}}}]);
\ No newline at end of file
+"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[21895],{88794:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>a,metadata:()=>i,toc:()=>l});var s=t(74848),r=t(28453);const a={},o="Create Sample Project",i={id:"tutorials/sampleproject",title:"Create Sample Project",description:"This tutorial walks through creating a sample geoprocessing project for the Federated States of Micronesia. It demonstrates multiple methods for doing spatial analysis and creating reports, from low-level to high-level, so that you can engage with it at any/all of the levels needed for your project.",source:"@site/docs/tutorials/sampleproject.md",sourceDirName:"tutorials",slug:"/tutorials/sampleproject",permalink:"/geoprocessing/docs/next/tutorials/sampleproject",draft:!1,unlisted:!1,editUrl:"https://github.com/seasketch/geoprocessing/tree/main/website/templates/shared/docs/tutorials/sampleproject.md",tags:[],version:"current",frontMatter:{},sidebar:"tutorialSidebar",previous:{title:"System Setup",permalink:"/geoprocessing/docs/next/tutorials/"},next:{title:"Create New Project",permalink:"/geoprocessing/docs/next/tutorials/newproject"}},c={},l=[{value:"Initialize Geoprocessing Project",id:"initialize-geoprocessing-project",level:2},{value:"Create Git repo",id:"create-git-repo",level:2},{value:"Preprocessing",id:"preprocessing",level:2},{value:"Testing",id:"testing",level:3},{value:"Simple Report",id:"simple-report",level:2},{value:"simpleFunction",id:"simplefunction",level:3},{value:"SimpleReport",id:"simplereport",level:3},{value:"Language Translation",id:"language-translation",level:3},{value:"Generate Examples",id:"generate-examples",level:3},{value:"Run test suite",id:"run-test-suite",level:3},{value:"Storybook",id:"storybook",level:3},{value:"Simple Function Modifications",id:"simple-function-modifications",level:3},{value:"Simple Report Modification",id:"simple-report-modification",level:3},{value:"First Project Build",id:"first-project-build",level:3},{value:"Reef Report",id:"reef-report",level:2},{value:"Import Data",id:"import-data",level:3},{value:"Precalc Data",id:"precalc-data",level:3},{value:"Create Report",id:"create-report",level:3},{value:"Add to Tab Report",id:"add-to-tab-report",level:3},{value:"Benthic Habitat Report",id:"benthic-habitat-report",level:2},{value:"Import Data",id:"import-data-1",level:3},{value:"Precalc Data",id:"precalc-data-1",level:3},{value:"World Geography",id:"world-geography",level:3},{value:"Add Metric Group",id:"add-metric-group",level:3},{value:"Create Report",id:"create-report-1",level:3},{value:"Test New Example Sketch",id:"test-new-example-sketch",level:3},{value:"Add To Tab Report",id:"add-to-tab-report-1",level:3},{value:"Seamount Report",id:"seamount-report",level:2},{value:"Import Data",id:"import-data-2",level:3},{value:"Precalc Data",id:"precalc-data-2",level:3},{value:"Add Objective",id:"add-objective",level:3},{value:"Add Metric Group",id:"add-metric-group-1",level:3},{value:"Create Report",id:"create-report-2",level:3},{value:"Add To Tab Report",id:"add-to-tab-report-2",level:3},{value:"Data Complexity",id:"data-complexity",level:3},{value:"Coral Species Report",id:"coral-species-report",level:2},{value:"Import Data",id:"import-data-3",level:3},{value:"Precalc",id:"precalc",level:3},{value:"Add Metric Group",id:"add-metric-group-2",level:3},{value:"Add Objective",id:"add-objective-1",level:3},{value:"Create Report",id:"create-report-3",level:3},{value:"Add To Tab Report",id:"add-to-tab-report-3",level:3},{value:"Language Translation",id:"language-translation-1",level:3},{value:"Create Github Project",id:"create-github-project",level:2},{value:"What's Next",id:"whats-next",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,r.R)(),...e.components},{Details:a}=n;return a||function(e,n){throw new Error("Expected "+(n?"component":"object")+" `"+e+"` to be defined: you likely forgot to import, pass, or provide it.")}("Details",!0),(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.header,{children:(0,s.jsx)(n.h1,{id:"create-sample-project",children:"Create Sample Project"})}),"\n",(0,s.jsx)(n.p,{children:"This tutorial walks through creating a sample geoprocessing project for the Federated States of Micronesia. It demonstrates multiple methods for doing spatial analysis and creating reports, from low-level to high-level, so that you can engage with it at any/all of the levels needed for your project."}),"\n",(0,s.jsx)(n.p,{children:"The planning area for this example is defined as extending from the Micronesia baseline (coastline/shoreline) to the outer boundary of the Exclusive Economic Zone (200 nautical miles)."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"EEZ with land",src:t(94616).A+"",width:"2113",height:"1099"})}),"\n",(0,s.jsx)(n.p,{children:"This tutorial assumes:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Your ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/tutorials/",children:"system setup"})," is complete"]}),"\n",(0,s.jsx)(n.li,{children:"Your geoprocessing virtual environment is running (Devcontainer or WSL)"}),"\n",(0,s.jsx)(n.li,{children:"You have VSCode open in your virtual environment with a terminal pane open"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Have questions along the way? Start a ",(0,s.jsx)(n.a,{href:"https://github.com/seasketch/geoprocessing/discussions",children:"discussion"})," on Github"]}),"\n",(0,s.jsx)(n.h2,{id:"initialize-geoprocessing-project",children:"Initialize Geoprocessing Project"}),"\n",(0,s.jsxs)(n.p,{children:["Start the project ",(0,s.jsx)(n.code,{children:"init"})," process, which will download the framework, and collect required project metadata."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-sh",children:"cd /workspaces\nnpx @seasketch/geoprocessing@7.0.0-beta.13 init 7.0.0-beta.13\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"? Choose a name for your project\nfsm-reports-test\n? Please provide a short description of this project\nMicronesia reports\n? Source code repository location\n[LEAVE BLANK]\n? Your name\n[YOUR_NAME]\n? Your email\n[YOUR_EMAIL]\n? Organization name (optional)\nExample organization\n? What software license would you like to use?\nBSD-3-Clause\n? What AWS region would you like to deploy functions in?\nus-west-1\n? What languages will your reports be published in, other than English? (leave blank for none)\nChuukese\nKosraean\n"})}),"\n",(0,s.jsx)(n.p,{children:"After pressing Enter, your project will be created and all NodeJS software dependencies installed. If your language is not present, you will be able to add it later."}),"\n",(0,s.jsx)(n.p,{children:"Now, re-open VSCode one level deeper, in your project folder::"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"File -> Open Folder\nType /workspaces/fsm-reports-test/\nPress Ctrl-J or Ctrl-backtick to open a new terminal\n"})}),"\n",(0,s.jsx)(n.h2,{id:"create-git-repo",children:"Create Git repo"}),"\n",(0,s.jsx)(n.p,{children:"Before you continue, let's create a local git repository and commit everything so far as a starting point."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:'git init\ngit add .\ngit commit -m "first commit"\ngit branch -M main\n'})}),"\n",(0,s.jsxs)(n.p,{children:["After this point, you can continue using git commands in the terminal to stage code changes and commit them if that's what you know, or you can use VSCode's ",(0,s.jsx)(n.a,{href:"https://code.visualstudio.com/docs/sourcecontrol/overview",children:"built-in git support"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["To learn more about your projects folder structure, visit the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/structure",children:"structure"})," page."]}),"\n",(0,s.jsx)(n.h2,{id:"preprocessing",children:"Preprocessing"}),"\n",(0,s.jsx)(n.p,{children:'Preprocessing functions are invoked by the SeaSketch platform, on a user-drawn shape, right after the user finishes drawing it. It\'s a specialized function that validates a drawn shape and potentially modifies it, such as to remove portions of the shape outside the planning boundary. This "clipping" of the shape is useful in that it allows a user to overdraw beyond the planning boundary and it will be clipped right to the edge of that boundary.'}),"\n",(0,s.jsx)(n.p,{children:"Here is an example of a preprocessor clipping a user drawn polygon to erase any part overlapping with land"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Before Clip"}),(0,s.jsx)(n.th,{children:"After Clip"})]})}),(0,s.jsx)(n.tbody,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:(0,s.jsx)(n.img,{alt:"Before",src:t(58667).A+"",title:"Before",width:"885",height:"708"})}),(0,s.jsx)(n.td,{children:(0,s.jsx)(n.img,{alt:"After",src:t(41538).A+"",title:"After",width:"773",height:"618"})})]})})]}),"\n",(0,s.jsxs)(n.p,{children:["In the ",(0,s.jsx)(n.code,{children:"src/functions"})," directory you will find four preprocessing functions that come with every project, and they are further configureable to meet your needs:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"validatePolygon"})," - verifies shape is not self-crossing, is at least a certain size (default to 500 square meters) and no larger than a certain size (defaults to 1 million square kilometers)."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"clipToLand"})," - clips the shape to just the portion on land, as defined by OpenStreeMap land polygons. Includes validatePolygon."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"clipToOcean"})," - clips the shape to remove the portion on land, as defined by OpenStreetMap land polygons. Includes validatePolygon."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"clipToOceanEez"})," - clips the shape to keep the portion within the boundary from the coastline to the outer boundary of the EEZ. Includes validatePolygon."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["These functions use datasources published by the ",(0,s.jsx)(n.a,{href:"https://github.com/seasketch/global-datasources/tree/main",children:"global datasources"})," project. These datasources can be replaced with more authoritative ones in your own projects, or you can follow the instructions on the website to export the specific subset of the data relevant to your project, and import it directly into your project."]}),"\n",(0,s.jsx)(n.h3,{id:"testing",children:"Testing"}),"\n",(0,s.jsx)(n.p,{children:"Each preprocessing function has its own unit test and smoke test file. For example:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Unit: ",(0,s.jsx)(n.code,{children:"src/functions/validatePolygon.test.ts"})]}),"\n",(0,s.jsxs)(n.li,{children:["Smoke: ",(0,s.jsx)(n.code,{children:"src/functions/validatePolygonSmoke.test.ts"})]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Unit tests"})," ensure the preprocessor produces exact output for very specific input features and configuration, and throws errors properly."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.strong,{children:"Smoke tests"}),' are about ensuring the preprocessor behaves properly for your project location, and that its results "look right" for a variety of input features. It does this by loading example shapes from the project ',(0,s.jsx)(n.code,{children:"examples/features"})," directory. It then runs the preprocessing function on the examples, makes sure they produce output, and saves them to ",(0,s.jsx)(n.code,{children:"examples/output"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"To test your preprocessing functions, we need to create example features within the extent of our Micronesian planning area. To do this, run the following script:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:'npx tsx scripts/genRandomPolygon.ts --outDir examples/features --filename polygon1.json --bbox "[135.31244183762126,-1.1731109652985907,165.67652822599732,13.445432925389298]"\nnpx tsx scripts/genRandomPolygon.ts --outDir examples/features --filename polygon2.json --bbox "[135.31244183762126,-1.1731109652985907,165.67652822599732,13.445432925389298]"\n'})}),"\n",(0,s.jsxs)(n.p,{children:["This will output an example Feature and an example FeatureCollection to ",(0,s.jsx)(n.code,{children:"examples/features"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Now run the tests:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm test\n"})}),"\n",(0,s.jsxs)(n.p,{children:["You can now look at the geojson output files in the ",(0,s.jsx)(n.code,{children:"examples/output"})," directory, including visually by opening them in QGIS or pasting the JSON into geojson.io. This is the best way to visually verify the preprocessor worked as expected."]}),"\n",(0,s.jsx)(n.p,{children:"This is a good checkpoint to commit your latest changes to Github."}),"\n",(0,s.jsxs)(n.p,{children:["To learn more about preprocessing, check out the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/preprocessing",children:"guide"})]}),"\n",(0,s.jsx)(n.h2,{id:"simple-report",children:"Simple Report"}),"\n",(0,s.jsx)(n.p,{children:"Your new project comes with a simple report that calculates the area of a sketch or sketch collection and presents it in a human readable format. Let's look at the pieces that go into this report."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Simple Card View",src:t(44424).A+"",width:"964",height:"661"})}),"\n",(0,s.jsx)(n.h3,{id:"simplefunction",children:"simpleFunction"}),"\n",(0,s.jsxs)(n.p,{children:["The area calculation is done within a geoprocessing function in ",(0,s.jsx)(n.code,{children:"src/functions/simpleFunction.ts"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Geoprocessing functions are invoked by a report client, as soon as its loaded in the browser by SeaSketch. It's a specialized function that takes a Sketch polygon or collection of Sketch polygons, performs some analysis, and returns the result to be displayed in the report client."}),"\n",(0,s.jsxs)(n.p,{children:["Open ",(0,s.jsx)(n.code,{children:"src/functions/simpleFunction.ts"})," and you will notice this function defines a custom result payload called ",(0,s.jsx)(n.code,{children:"SimpleResults"}),", which in this case is a Javascript object with an ",(0,s.jsx)(n.code,{children:"area"})," property containing a number value."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"export interface SimpleResults {\n /** area of sketch within geography in square meters */\n area: number;\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"simpleFunction"})," starts off with the basic signature of a geoprocessing function. It accepts a ",(0,s.jsx)(n.code,{children:"sketch"})," parameter that is either a single ",(0,s.jsx)(n.code,{children:"Sketch"})," polygon or a ",(0,s.jsx)(n.code,{children:"SketchCollection"})," with multiple Sketch polygons. Unless your planning project only requires users to design single sketches and not collections, your geoprocessing function must be able to handle both."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"async function simpleFunction(\n sketch:\n | Sketch\n | SketchCollection,\n): Promise {\n"})}),"\n",(0,s.jsx)(n.p,{children:"The function then performs its analysis and returns the result."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"// Add analysis code\nconst sketchArea = area(sketch);\n\n// Custom return type\nreturn {\n area: sketchArea,\n};\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Below that, a new ",(0,s.jsx)(n.code,{children:"GeoprocessingHandler"})," is instantiated, with simpleFunction passed into it. Behind the scenes, this wraps simpleFunction in an AWS Lambda handler function, which once deployed to AWS, allows the geoprocessing function to be invoked using an API call, by a report client running in a web browser."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'export default new GeoprocessingHandler(simpleFunction, {\n title: "simpleFunction",\n description: "Function description",\n timeout: 60, // seconds\n memory: 1024, // megabytes\n executionMode: "async",\n});\n'})}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"GeoprocessingHandler"})," requires a ",(0,s.jsx)(n.code,{children:"title"})," and ",(0,s.jsx)(n.code,{children:"description"}),", which uniquely identifies the function that will be published by your project. It also accepts some additional parameters defining what resources the Lamda should have, and its behavior:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"timeout"}),": how many seconds the Lambda will run before it times out in error."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"memory"}),": memory allocated to the Lambda, can go up to 10,240 MB. Number of processors increase with memory size automatically."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"executionMode"}),": determines how the report client waits for geoprocessing function results, defaults to async. Sync - wait with connection open for immediate results, Async - wait for web socket message that results are ready, then fetch. Sync should only be used for very fast geoprocessing functions (1-2 seconds max). Think of it as a performance optimization."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"You can change all these parameter values to suit your needs, but the default values are suitable for now."}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"simpleFunction"})," is already registered as a geoprocessing function in ",(0,s.jsx)(n.code,{children:"project/geoprocessing.json"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"Now let's look at the browser report client that invokes this function."}),"\n",(0,s.jsx)(n.h3,{id:"simplereport",children:"SimpleReport"}),"\n",(0,s.jsxs)(n.p,{children:["A report client is a top-level React component for rendering a report in the users web browser. Report clients are located in the ",(0,s.jsx)(n.code,{children:"src/clients"})," directory and are responsible for the layout of one or more ",(0,s.jsx)(n.code,{children:"Card"})," components. Cards are able to invoke geoprocessing functions and display their results."]}),"\n",(0,s.jsx)(n.p,{children:"The two report clients that come with your project are:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"SimpleReport.tsx"})," - simple one page report client containing a SketchAttributesCard and a SimpleCard."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"TabReport.tsx"})," - more complex multi-page report layout controlled by a tab switcher component, so that only one page is in view at a time."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Both these report clients are already registered in ",(0,s.jsx)(n.code,{children:"project/geoprocessing.json"}),". To start, let's focus on ",(0,s.jsx)(n.code,{children:"SimpleReport"})," and ",(0,s.jsx)(n.code,{children:"SimpleCard"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:"export const SimpleReport = () => {\n return (\n \n \n \n \n );\n};\n"})}),"\n",(0,s.jsxs)(n.p,{children:["SimpleReport renders two cards, ",(0,s.jsx)(n.code,{children:"SimpleCard"})," and ",(0,s.jsx)(n.code,{children:"SketchAttributesCard"}),", wrapping them in a languge ",(0,s.jsx)(n.code,{children:"Translator"})," component (you will learn more about this later)."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"SketchAttributes"})," card is a card component that displays the properties of the users Sketch. No geoprocessing function is needed to do its work."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"SimpleCard"})," is a card component that invokes simpleFunction and displays its results. Let's look at it closer."]}),"\n",(0,s.jsxs)(n.p,{children:["The first thing to notice is that SimpleCard renders a ",(0,s.jsx)(n.code,{children:"ResultsCard"})," component. Behind the scenes ResultsCard invokes the geoprocessing function with the ",(0,s.jsx)(n.code,{children:"functionName"})," provided (simpleFunction). Keep in mind that in a production environment the ResultsCard is rendered in your web browser and the geoprocessing function is a Lambda function in Amazon's cloud invoked via an API call."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'\n'})}),"\n",(0,s.jsx)(n.p,{children:"ResultsCard then contains a render function that is provided with the results."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'{\n (data: SimpleResults) => {\n const areaSqKm = data.area / 1_000_000;\n const areaString = roundDecimalFormat(areaSqKm, 0, {\n keepSmallValues: true,\n });\n const sketchStr = isCollection ? t("sketch collection") : t("sketch");\n\n return (\n <>\n
\n \n This {{ sketchStr }} is {{ areaString }} square kilometers.\n \n
\n >\n );\n };\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["This render function takes an input parameter ",(0,s.jsx)(n.code,{children:"data"})," that has the same type (",(0,s.jsx)(n.code,{children:"SimpleResults"}),") as the return type of ",(0,s.jsx)(n.code,{children:"simpleFunction"}),". This gives you fully typed access to your report results."]}),"\n",(0,s.jsxs)(n.p,{children:["The code in this render function is the heart of each report card. This particular card takes the ",(0,s.jsx)(n.code,{children:"area"})," value it is given in square meters, and converts it to square kilometers. It then rounds it to a whole number, and formats it to make it more readable. Also notice that it renders a slightly different message depending on whether it is a single sketch or a sketch collection being reported on."]}),"\n",(0,s.jsx)(n.h3,{id:"language-translation",children:"Language Translation"}),"\n",(0,s.jsxs)(n.p,{children:["The last thing to notice is that SimpleCard contains a lot of boilerplate for language translation of its strings (using ",(0,s.jsx)(n.a,{href:"https://react.i18next.com/",children:(0,s.jsx)(n.code,{children:"react-i18next"})}),"). If your reports need to be multi-lingual you will need to to use these, otherwise you can drop them."]}),"\n",(0,s.jsx)(n.p,{children:"Language translation is a multi-part process:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["First, a combination of ",(0,s.jsx)(n.code,{children:"useTranslation"}),", ",(0,s.jsx)(n.code,{children:"t"})," function, and ",(0,s.jsx)(n.code,{children:"Trans"})," components are used to establish which strings in your report client and components should be translated."]}),"\n",(0,s.jsxs)(n.li,{children:["Next, those translateable strings are extracted using the ",(0,s.jsx)(n.code,{children:"extract:translation"})," command, and output to ",(0,s.jsx)(n.code,{children:"src/i18n/lang/en/translation.json"}),". The strings extracted for SimpleCard are:"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:'{\n "sketch": "sketch",\n "sketch collection": "sketch collection",\n "SimpleCard sketch size message": "This {{sketchStr}} is {{areaString}} square kilometers.",\n}\n'})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"The English translation file is then translated to the other needed languages and put into their own translation files"}),"\n",(0,s.jsxs)(n.li,{children:["The ",(0,s.jsx)(n.code,{children:"Translator"})," component in your report client is then responsible for inspecting the users language at runtime in the browser and swapping the English strings for strings in the appropriate language."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"This process is covered in more detail in a separate doc."}),"\n",(0,s.jsx)(n.h3,{id:"generate-examples",children:"Generate Examples"}),"\n",(0,s.jsxs)(n.p,{children:["With a working geoprocessing function and report client already in place, you're ready to generate example sketches for testing them. We'll use the same ",(0,s.jsx)(n.code,{children:"genRandomPolygon"})," script as before. But let's look closer at how we figured out the bounding box extent of the Micronesian planning area. First, use ogrinfo to inspect the Micronesia EEZ polygon data layer in your data package."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"ogrinfo -so -json data/src/eez_withland_mr.fgb\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Deep in its output you will see a ",(0,s.jsx)(n.code,{children:"geometryFields"})," property, which contains the bounding box extent of the EEZ feature. Use the ",(0,s.jsx)(n.code,{children:"jq"})," utility to extract this extent:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"ogrinfo -so -json data/src/eez_withland_mr.fgb | jq -c .layers[0].geometryFields[0].extent\n[135.31244183762126,-1.1731109652985907,165.67652822599732,13.445432925389298]\n"})}),"\n",(0,s.jsx)(n.p,{children:"This will output an array with the extent of the EEZ. This is just one of multiple possible methods to get this extent. You are welcome to use the method that works best for you."}),"\n",(0,s.jsx)(n.p,{children:"Now run the genRandomPolygon script with this extent. The following examples will create a Sketch polygon, and then a SketchCollection containing 10 Sketch polygons."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:'npx tsx scripts/genRandomPolygon.ts --outDir examples/sketches --filename sketch1.json --bbox "[135.31244183762126,-1.1731109652985907,165.67652822599732,13.445432925389298]" --bboxShrinkFactor 5 --sketch\nnpx tsx scripts/genRandomPolygon.ts --outDir examples/sketches --filename sketchCollection1.json --bbox "[135.31244183762126,-1.1731109652985907,165.67652822599732,13.445432925389298]" --bboxShrinkFactor 5 --sketch --numFeatures 10\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"--bboxShrinkFactor"})," argument used shrinks the height and width of the given bbox by a factor of 5, and then generates random features that are within that reduced bbox. A suitable shrink factor value was discovered through trial and error. Simply visualize the resulting json file in QGIS or other software and find a value that produces polygons that are completely within the planning area polygon. (see image below)."]}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.img,{alt:"EEZ bbox",src:t(81465).A+"",width:"1278",height:"661"}),"\nImage: cluster of 10 random sketches (in orange) within Micronesia EEZ"]}),"\n",(0,s.jsxs)(n.p,{children:["Learn more about the options for ",(0,s.jsx)(n.code,{children:"genRandomPolygon"})," by running:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{children:"npx tsx scripts/genRandomPolygon.ts --help\n"})}),"\n",(0,s.jsx)(n.h3,{id:"run-test-suite",children:"Run test suite"}),"\n",(0,s.jsxs)(n.p,{children:["Now that you have example features and sketches, you can test ",(0,s.jsx)(n.code,{children:"simpleFunction"}),". Run the test suite now:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm test\n"})}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Using ",(0,s.jsx)(n.code,{children:"simpleFunctionSmoke.test.ts"}),", simpleFunction will be run against all of the polygon Sketches in ",(0,s.jsx)(n.code,{children:"examples/sketches"}),"."]}),"\n",(0,s.jsxs)(n.li,{children:["The results of all smokes tests are output to the ",(0,s.jsx)(n.code,{children:"examples/output"})," directory."]}),"\n",(0,s.jsx)(n.li,{children:"You can inspect the output files, and see the calculated area values for each sketch input."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Commit the output files to your git repository at this time."}),"\n",(0,s.jsxs)(n.p,{children:["You can make changes to simpleFunction, then rerun tests to regenerate them at any time, and delete any that are stale and no longer needed. For advanced use, check out the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/testing",children:"testing"})," guide."]}),"\n",(0,s.jsx)(n.h3,{id:"storybook",children:"Storybook"}),"\n",(0,s.jsx)(n.p,{children:"Storybook is used to view your reports."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run storybook\n"})}),"\n",(0,s.jsx)(n.p,{children:"This will:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["Generate a story for every combination of report client registered in ",(0,s.jsx)(n.code,{children:"project/geoprocessing.json"})," and sketch present in ",(0,s.jsx)(n.code,{children:"examples/sketches"}),"."]}),"\n",(0,s.jsx)(n.li,{children:"Load all of the smoke test output for every sketch (to load in stories instead of running geoprocessing functions)"}),"\n",(0,s.jsx)(n.li,{children:"Start the storybook server and give you the URL."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Open the storybook URL in your browser and click through the stories."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Simple Card View",src:t(44424).A+"",width:"964",height:"661"})}),"\n",(0,s.jsx)(n.p,{children:"A powerful feature of Storybook is that when you save edits to your report client or component code, storybook will refresh the browser automatically with the changes. This lets you develop your reports and debug them more quickly."}),"\n",(0,s.jsx)(n.p,{children:"There are a couple of situations that will cause you to need to stop your storybook server (Ctrl-C) and then restart it to pick up the changes."}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["you add more sketch examples to your ",(0,s.jsx)(n.code,{children:"examples/sketch"})," directory"]}),"\n",(0,s.jsx)(n.li,{children:"you rerun smoke tests and generate new test output"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Learn more in the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/tutorials/storybook",children:"storybook guide"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"simple-function-modifications",children:"Simple Function Modifications"}),"\n",(0,s.jsx)(n.p,{children:"Let's enhance your simple geoprocessing function to calculate more detailed information when the report is run on a sketch collection. It should calculate the area of the entire collection, and the area of each child sketch in the collection."}),"\n",(0,s.jsxs)(n.p,{children:["First modify SimpleResults with an additional property ",(0,s.jsx)(n.code,{children:"childSketchAreas"})," that can store this information:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"export interface SimpleResults {\n /** area of reef within sketch in square meters */\n area: number;\n childSketchAreas: {\n /** Name of the sketch */\n name: string;\n /** Area of the sketch in square meters */\n area: number;\n }[];\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Then calculate the additional values and return them in the result payload:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// Add analysis code\nconst sketchArea = area(sketch);\n\nlet childSketchAreas: SimpleResults["childSketchAreas"] = [];\nif (sketch.properties.isCollection) {\n childSketchAreas = toSketchArray(sketch).map((sketch) => ({\n name: sketch.properties.name,\n area: area(sketch),\n }));\n}\n\n// Custom return type\nreturn {\n area: sketchArea,\n childSketchAreas,\n};\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Here's what the final ",(0,s.jsx)(n.code,{children:"simpleFunction"})," code should look like:"]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/functions/simpleFunction.ts"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'import {\n Sketch,\n SketchCollection,\n Polygon,\n MultiPolygon,\n GeoprocessingHandler,\n toSketchArray,\n} from "@seasketch/geoprocessing";\nimport { area } from "@turf/turf";\n\nexport interface SimpleResults {\n /** area of reef within sketch in square meters */\n area: number;\n childSketchAreas: {\n /** Name of the sketch */\n name: string;\n /** Area of the sketch in square meters */\n area: number;\n }[];\n}\n\n/**\n * Simple geoprocessing function with custom result payload\n */\nasync function simpleFunction(\n sketch:\n | Sketch\n | SketchCollection,\n): Promise {\n // Add analysis code\n const sketchArea = area(sketch);\n\n let childSketchAreas: SimpleResults["childSketchAreas"] = [];\n if (sketch.properties.isCollection) {\n childSketchAreas = toSketchArray(sketch).map((sketch) => ({\n name: sketch.properties.name,\n area: area(sketch),\n }));\n }\n\n // Custom return type\n return {\n area: sketchArea,\n childSketchAreas,\n };\n}\n\nexport default new GeoprocessingHandler(simpleFunction, {\n title: "simpleFunction",\n description: "Function description",\n timeout: 60, // seconds\n memory: 1024, // megabytes\n executionMode: "async",\n});\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Run your tests again to generate the new smoke test output:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run test\n"})}),"\n",(0,s.jsx)(n.h3,{id:"simple-report-modification",children:"Simple Report Modification"}),"\n",(0,s.jsxs)(n.p,{children:["Now let's modify SimpleReportCard to display the new data. You will add a new ",(0,s.jsx)(n.code,{children:"Collapse"})," section with a ",(0,s.jsx)(n.code,{children:"Table"})," component that lists out the sketch areas by name."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:'
\n \n This {{ sketchStr }} is {{ areaString }} km\xb2.\n \n
\n{isCollection && (\n \n
\n roundDecimalFormat(row.area / 1_000_000, 0, {\n keepSmallValues: true,\n }),\n },\n ]}\n />\n \n)}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Here's what the final SimpleCard code should look like:"}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/components/SimpleCard.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-jsx",children:'import React from "react";\nimport { Trans, useTranslation } from "react-i18next";\nimport {\n Collapse,\n ResultsCard,\n Table,\n useSketchProperties,\n} from "@seasketch/geoprocessing/client-ui";\n// Import SimpleResults to type-check data access in ResultsCard render function\nimport { SimpleResults } from "../functions/simpleFunction.js";\nimport { roundDecimalFormat } from "@seasketch/geoprocessing/client-core";\n\nexport const SimpleCard = () => {\n const { t } = useTranslation();\n const [{ isCollection }] = useSketchProperties();\n const titleTrans = t("SimpleCard title", "Simple Report");\n return (\n <>\n \n {(data: SimpleResults) => {\n const areaSqKm = data.area / 1_000_000;\n const areaString = roundDecimalFormat(areaSqKm, 0, {\n keepSmallValues: true,\n });\n const sketchStr = isCollection ? t("sketch collection") : t("sketch");\n\n return (\n <>\n
\n \n This {{ sketchStr }} is {{ areaString }} km\xb2.\n \n
\n {isCollection && (\n \n
\n roundDecimalFormat(row.area / 1_000_000, 0, {\n keepSmallValues: true,\n }),\n },\n ]}\n />\n \n )}\n >\n );\n }}\n \n >\n );\n};\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"If your storybook is still running from last time, you will need to restart it to pick up the new smoke test output."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"Ctrl-C\nnpm run storybook\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Your updated report should have a new collapsible table, that when expanded looks like the following:\n",(0,s.jsx)(n.img,{alt:"Simple Card with table",src:t(51899).A+"",width:"747",height:"679"})]}),"\n",(0,s.jsx)(n.h3,{id:"first-project-build",children:"First Project Build"}),"\n",(0,s.jsxs)(n.p,{children:["Now that you have confirmed your function is working properly, and your report client displays properly for a variety of example sketches, you are ready to do your first build. The application ",(0,s.jsx)(n.code,{children:"build"})," proceess packages it for deployment. Specifically it:"]}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Checks all the Typescript code to make sure it's valid and types are used properly."}),"\n",(0,s.jsx)(n.li,{children:"Transpiles all Typescript to Javascript"}),"\n",(0,s.jsxs)(n.li,{children:["Bundles UI report clients into the ",(0,s.jsx)(n.code,{children:".build-web"})," directory"]}),"\n",(0,s.jsxs)(n.li,{children:["Bundles geoprocessing and preprocessing functions into the ",(0,s.jsx)(n.code,{children:".build"})," directory."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"To build your application run the following:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run build\n"})}),"\n",(0,s.jsx)(n.p,{children:"Once your build is successful, you should stage and commit all your changes to git."}),"\n",(0,s.jsx)(n.h2,{id:"reef-report",children:"Reef Report"}),"\n",(0,s.jsx)(n.p,{children:"Next you will create a coral reef report that uses the reef extent dataset. Here is an image of it displayed in QGIS. Notice that the coral is entirely in shallow water around the island coastline and atolls."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Reef Extent",src:t(35162).A+"",width:"2053",height:"1024"})}),"\n",(0,s.jsx)(n.h3,{id:"import-data",children:"Import Data"}),"\n",(0,s.jsx)(n.p,{children:"To access this datasource, first download a data package prepared for FSM to your project space and unzip it:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"wget -P data/src https://github.com/user-attachments/files/18030075/FSM_MSP_Data_Example_v2.zip\nunzip data/src/FSM_MSP_Data_Example_v2.zip -d data/src\nrm data/src/FSM_MSP_Data_Example_v2.zip\n"})}),"\n",(0,s.jsx)(n.p,{children:"Now import the datasource to your project."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run import:data\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"? Type of data?\nVector\n? Enter path to src file (with filename)\ndata/src/reefextent.fgb\n? Select layer to import\nreefextent\n? Choose unique datasource name (a-z, A-Z, 0-9, -, _), defaults to filename\nreefextent\n? Should multi-part geometries be split into single-part geometries?\nYes\n? (Optional) additional formats to create (besides fgb)\n[Press enter to skip]\n? Select feature properties that you want to group metrics by\n[Press enter to skip]\n? Select additional feature properties to keep in final datasource\n[Press enter to skip]\n? These formats are automatically created: fgb. Select any additional formats you want created\n[Press enter to skip]\n? Will you be precalculating summary metrics for this datasource after import? (Typically yes if reporting sketch % overlap with datasource)\nYes\n"})}),"\n",(0,s.jsx)(n.p,{children:"The import process will:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"reproject your data to the WGS84 reference system, if not already (required by Turf.JS)"}),"\n",(0,s.jsxs)(n.li,{children:["split any features that cross the 180 degree ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/antimeridian",children:"antimeridian"})]}),"\n",(0,s.jsx)(n.li,{children:"reduce the source dataset down to only the necessary attributes (saving network bandwidth later)"}),"\n",(0,s.jsxs)(n.li,{children:["output a new file in the cloud-optimized flatgeobuf format to the ",(0,s.jsx)(n.code,{children:"data/dist"})," directory."]}),"\n",(0,s.jsxs)(n.li,{children:["register the datasource in ",(0,s.jsx)(n.code,{children:"project/datasources.json"}),", along with additional metadata. This allows you to:","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:["quickly access project datasources in your reports using the ",(0,s.jsx)(n.code,{children:"projectClient"})," (more on this later)"]}),"\n",(0,s.jsxs)(n.li,{children:["quickly reimport datasources using the ",(0,s.jsx)(n.code,{children:"reimport:data"})," command, without having to answer questions again."]}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Once the import is finished, you are ready to use your datasources for ",(0,s.jsx)(n.code,{children:"local"})," report development. You can add, edit, or delete records in datasources.json manually to meet your need as long as the records meet the expected ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/concepts#datasources",children:"schema"}),"."]}),"\n",(0,s.jsx)(n.h3,{id:"precalc-data",children:"Precalc Data"}),"\n",(0,s.jsx)(n.p,{children:"Next, you will create a standalone script to calculate the total area of the polygons in the reef extent datasource for use in the report. By doing this calculation ahead of time, you won't need to do it every time your geoprocessing function runs. There is an automated way of precalculating the area of a datasource, but the purpose of this is to teach you a workflow for doing it on your own."}),"\n",(0,s.jsxs)(n.p,{children:["Create a new file with the following code and save it to ",(0,s.jsx)(n.code,{children:"scripts/coralReefPrecalc.ts"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// Run the following command from the project root directory\n// npx tsx scripts/coralReefPrecalc.ts\n\nimport { area } from "@turf/turf";\nimport { geojson } from "flatgeobuf";\nimport { readFileSync } from "fs";\nimport fs from "fs-extra";\n\n// Fetch all reef features and calculate total area\nconst buffer = readFileSync(\n `${import.meta.dirname}/../data/dist/reefextent.fgb`,\n);\nconst reefFeatures = geojson.deserialize(new Uint8Array(buffer));\nconst totalArea = area(reefFeatures);\n\nconst reefPrecalc = {\n totalAreaSqMeters: totalArea,\n};\n\nfs.ensureDirSync(`${import.meta.dirname}/../data/precalc`);\nfs.writeJsonSync(\n `${import.meta.dirname}/../data/precalc/reefextent.json`,\n reefPrecalc,\n);\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Now run it. Your shell needs to be in the root directory of your project to run this Typescript file directly using ",(0,s.jsx)(n.code,{children:"npx"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npx tsx scripts/coralReefPrecalc.ts\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The script fetches all features from the reef extent flatgeobuf file, calculates their total area and writes it to ",(0,s.jsx)(n.code,{children:"data/precalc/reefextent.json"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:'{\n "totalArea": 716100906.2570591\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"We are going to use this precalculated value in a geoprocessing function in the next step."}),"\n",(0,s.jsx)(n.h3,{id:"create-report",children:"Create Report"}),"\n",(0,s.jsx)(n.p,{children:"To create a blank report ready to build on, run the following:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run create:report\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"? Type of report to create\nBlank report - empty report ready to build from scratch\n\n? Describe what this reports geoprocessing function will calculate (e.g. Calculate sketch overlap with boundary polygons)\nCalculate sketch overlap with reef extent datasource\n\n? Title for this report, in camelCase\ncoralReef\n\n\u2714 Created coralReef report\n\u2714 Registered report assets in project/geoprocessing.json\n\nGeoprocessing function: src/functions/coralReef.ts\nSmoke test: src/functions/coralReefSmoke.test.ts\nReport component: src/components/CoralReefCard.tsx\nStory generator: src/components/CoralReefCard.example-stories.ts\n\nNext Steps:\n * 'npm test' to run smoke tests against your new geoprocessing function\n * 'npm run storybook' to view your new report with smoke test output\n * Add to a top-level report client or page when ready\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Open ",(0,s.jsx)(n.code,{children:"src/functions/coralReef.ts"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"You will now update this code to answer the following questions:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"What percentage of all coral reef is within the current sketch polygon (or sketch collection polygons)?"}),"\n",(0,s.jsx)(n.li,{children:"If it is a sketch collection, does it meet the planning objective of protecting 20% of all coral reef?"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Replace the existing code with the following:"}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/functions/coralReef.ts"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'import {\n Sketch,\n SketchCollection,\n Polygon,\n MultiPolygon,\n GeoprocessingHandler,\n getFeaturesForSketchBBoxes,\n toSketchArray,\n clipMultiMerge,\n isSketchCollection,\n clip,\n Feature,\n} from "@seasketch/geoprocessing";\nimport project from "../../project/projectClient.js";\nimport { area, featureCollection } from "@turf/turf";\nimport reefPrecalc from "../../data/precalc/reefextent.json";\n\nexport interface CoralReefResults {\n /** area of all reef extent polygons in square meters */\n totalArea: number;\n /** area of reef extent within sketch or sketch collection in square meters */\n sketchArea: number;\n childSketchAreas: {\n /** Name of the sketch */\n name: string;\n /** Area of reef extent within child sketch in square meters */\n area: number | null;\n }[];\n}\n\n/**\n * Simple geoprocessing function with custom result payload\n */\nexport async function coralReef(\n sketch:\n | Sketch\n | SketchCollection,\n): Promise {\n // Load just the reef features that intersect with the sketch bounding box\n // or in case of a sketch collection, the child sketch bounding boxes\n const ds = project.getInternalVectorDatasourceById("reefextent");\n const url = project.getDatasourceUrl(ds);\n const reefFeatures: Feature[] =\n await getFeaturesForSketchBBoxes(sketch, url);\n\n // Add analysis code\n\n // If collection, calculate area of each sketches intersection with reef\n let childSketchAreas: CoralReefResults["childSketchAreas"] = [];\n if (sketch.properties.isCollection) {\n childSketchAreas = toSketchArray(sketch).map((sketch) => {\n const sketchReefOverlap =\n reefFeatures.length > 0\n ? clipMultiMerge(\n sketch,\n featureCollection(reefFeatures),\n "intersection",\n )\n : null;\n return {\n name: sketch.properties.name,\n area: sketchReefOverlap ? area(sketchReefOverlap) : 0,\n };\n });\n }\n\n // Calculate area of overall sketch intersection with reef\n const sketchArea = (() => {\n // Figure out feature to clip\n let clipFeature: Feature | null;\n if (reefFeatures.length === 0) {\n return 0;\n } else if (isSketchCollection(sketch)) {\n // union sketches to remove overlap and avoid double count\n clipFeature = clip(sketch, "union");\n if (!clipFeature) return 0;\n } else {\n clipFeature = sketch;\n }\n //Merge reefFeatures into a single multipolygon, then intersect\n const sketchReefOverlap =\n reefFeatures.length > 0\n ? clipMultiMerge(\n clipFeature,\n featureCollection(reefFeatures),\n "intersection",\n )\n : null;\n return sketchReefOverlap ? area(sketchReefOverlap) : 0;\n })();\n\n // Custom return type\n return {\n totalArea: reefPrecalc.totalAreaSqMeters,\n sketchArea: sketchArea,\n childSketchAreas,\n };\n}\n\nexport default new GeoprocessingHandler(coralReef, {\n title: "coralReef",\n description: "calculate sketch overlap with reef extent datasource",\n timeout: 60, // seconds\n memory: 1024, // megabytes\n executionMode: "async",\n});\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Notice that the code imports the totalArea value you precalculated and inserts it into the result payload, avoiding the need to recalculate it each time."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'import reefPrecalc from "../../data/precalc/reefextent.json";\n\nreefPrecalc.totalArea;\n'})}),"\n",(0,s.jsx)(n.p,{children:"Then it fetches only the reef features whose bounding box intersects with the sketch bounding box, or in case of a sketch collection, that intersects with each of its child sketch bounding boxes. This is more efficient than fetching the entire reef dataset, saving time and network bandwidth."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// Load just the reef features that intersect with the sketch bounding box\n// or in case of a sketch collection, the child sketch bounding boxes\nconst ds = project.getInternalVectorDatasourceById("reefextent");\nconst url = project.getDatasourceUrl(ds);\nconst reefFeatures: Feature[] =\n await getFeaturesForSketchBBoxes(sketch, url);\n'})}),"\n",(0,s.jsxs)(n.p,{children:["Next, if the sketch is a collection, it calculates how much coral reef overlaps with each individual sketch. To do this, it needs to figure out the areas where the sketches and coral reef ",(0,s.jsx)(n.code,{children:"intersect"}),". This is calculated using the ",(0,s.jsx)(n.code,{children:"clipMultiMerge"})," function. It is essential that this function is used because it merges the reefFeatures collection into a single multipolygon before intersecting it with the sketch. If you were to use the ",(0,s.jsx)(n.code,{children:"clip"})," function you would need to loop through each reef feature and clip the sketch to it."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// If collection, calculate area of each sketches intersection with reef\nlet childSketchAreas: CoralReefResults["childSketchAreas"] = [];\nif (sketch.properties.isCollection) {\n childSketchAreas = toSketchArray(sketch).map((sketch) => {\n const sketchReefOverlap =\n reefFeatures.length > 0\n ? clipMultiMerge(\n sketch,\n featureCollection(reefFeatures),\n "intersection",\n )\n : null;\n return {\n name: sketch.properties.name,\n area: sketchReefOverlap ? area(sketchReefOverlap) : 0,\n };\n });\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Finally, it calculates how much coral reef overlaps with the entire sketch/collection."}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"If there is no overlap between the reef and sketch, then it simply returns zero."}),"\n",(0,s.jsxs)(n.li,{children:["If it's a sketch collection it first performs a ",(0,s.jsx)(n.code,{children:"union"})," operation that merges all of the sketches into a single Multipolygon, dissolving any overlap between the sketches so that area is not double counted."]}),"\n",(0,s.jsx)(n.li,{children:"If it's a single sketch polygon then it just calculates its area and returns it."}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// Calculate area of overall sketch intersection with reef\nconst sketchArea = (() => {\n // Figure out feature to clip\n let clipFeature: Feature | null;\n if (reefFeatures.length === 0) {\n return 0;\n } else if (isSketchCollection(sketch)) {\n // union sketches to remove overlap and avoid double count\n clipFeature = clip(sketch, "union");\n if (!clipFeature) return 0;\n } else {\n clipFeature = sketch;\n }\n //Merge reefFeatures into a single multipolygon, then intersect\n const sketchReefOverlap =\n reefFeatures.length > 0\n ? clipMultiMerge(\n clipFeature,\n featureCollection(reefFeatures),\n "intersection",\n )\n : null;\n return sketchReefOverlap ? area(sketchReefOverlap) : 0;\n})();\n\n// Custom return type\nreturn {\n totalArea: reefPrecalc.totalAreaSqMeters,\n sketchArea: sketchArea,\n childSketchAreas,\n};\n'})}),"\n",(0,s.jsx)(n.p,{children:"Now run tests to generate updated output for each of the sample sketches:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run test\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Confirm that the output looks as expected. It is possible none of your test sketches will overlap with any coral reef features in which case all area values will have a ",(0,s.jsx)(n.code,{children:"0"})," value."]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"examples/output/sketchCollection1/coralReef.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:'{\n "totalArea": 716100906.2570591,\n "sketchArea": 367734.86730626615,\n "childSketchAreas": [\n {\n "name": "sketchCollection1-1",\n "area": 428611581.5348215\n },\n {\n "name": "sketchCollection1-2",\n "area": 258701691.8012635\n },\n {\n "name": "sketchCollection1-3",\n "area": 599831752.2377243\n },\n {\n "name": "sketchCollection1-4",\n "area": 372585470.74404347\n },\n {\n "name": "sketchCollection1-5",\n "area": 562781719.588172\n },\n {\n "name": "sketchCollection1-6",\n "area": 528237794.83984125\n },\n {\n "name": "sketchCollection1-7",\n "area": 253970548.59694752\n },\n {\n "name": "sketchCollection1-8",\n "area": 376674659.1741572\n },\n {\n "name": "sketchCollection1-9",\n "area": 657788539.6501052\n },\n {\n "name": "sketchCollection1-10",\n "area": 712233449.0549812\n }\n ]\n}\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Now open src/components/CoralReefCard.tsx."}),"\n",(0,s.jsx)(n.p,{children:"You will now update this code to:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Display the % of total coral reef captured within this sketch"}),"\n",(0,s.jsxs)(n.li,{children:["If it is a sketch collection","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Indicate whether the objective of protecting 20% of all coral reef has been met."}),"\n",(0,s.jsx)(n.li,{children:"Display a collapsible area with a breakdown of the area and % area of coral reef within each individual sketch in the collection."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Replace the existing code with the following:"}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/components/CoralReefCard.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-javascript",children:'import React from "react";\nimport { Trans, useTranslation } from "react-i18next";\nimport {\n ResultsCard,\n useSketchProperties,\n HorizontalStackedBar,\n Collapse,\n Table,\n ObjectiveStatus,\n VerticalSpacer,\n} from "@seasketch/geoprocessing/client-ui";\nimport {\n percentWithEdge,\n roundDecimalFormat,\n squareMeterToKilometer,\n} from "@seasketch/geoprocessing/client-core";\n\n// Import CoralReefResults to type-check data access in ResultsCard render function\nimport { CoralReefResults } from "../functions/coralReef.js";\n\nexport const CoralReefCard = () => {\n const { t } = useTranslation();\n const [{ isCollection }] = useSketchProperties();\n const titleTrans = t("CoralReefCard title", "Coral Reef");\n return (\n <>\n \n {(data: CoralReefResults) => {\n const target = 0.2; // 20%\n const reefPerc = data.sketchArea / data.totalArea;\n const reefPercString = percentWithEdge(reefPerc);\n const targetPercString = percentWithEdge(target);\n\n const meetsObjective = reefPerc >= target;\n\n // Adjust values for chart to be in range 0-100\n const chartRows = [[[reefPerc * 100]]];\n\n const sketchTypeStr = isCollection\n ? t("sketch collection")\n : t("sketch");\n\n const meetsOrNotElement = meetsObjective ? (\n \n This {{ sketchTypeStr }} meets the objective of protecting{" "}\n {{ targetPercString }} of coral reef\n \n ) : (\n \n This {{ sketchTypeStr }} does not meet the objective of protecting{" "}\n {{ targetPercString }} of coral reef\n \n );\n\n return (\n <>\n
\n \n {{ reefPercString }} of all Micronesia coral reef is within\n this {{ sketchTypeStr }}.\n \n
\n roundDecimalFormat(squareMeterToKilometer(row.area)),\n },\n {\n Header: t("% Reef within Sketch"),\n accessor: (row: any) =>\n percentWithEdge(row.area / data.totalArea),\n },\n ]}\n />\n \n )}\n >\n );\n }}\n \n >\n );\n};\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"There are multiple things worth noticing:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"squareMeterToKilometer"})," conversion helper function is used"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"percentWithEdge"})," and ",(0,s.jsx)(n.code,{children:"roundDecimalFormat"})," helper functions are used to format values to be more human readable. Will use locale settings of the users browser when formatting decimal and percent."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"HorizontalStackedBar"})," and ",(0,s.jsx)(n.code,{children:"ObjectiveStatus"})," core UI components present information in a more visually interesting way that can be reused across reports. See core ",(0,s.jsx)(n.a,{href:"/storybook",children:"storybook"})," for more examples of their use."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Now, start storybook and view the result. You will find the CoralReefCard under the ",(0,s.jsx)(n.code,{children:"Components"})," section:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run storybook\n"})}),"\n",(0,s.jsx)(n.p,{children:"When viewing a sketch example, it should display the following:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CoralReefCard sketch view",src:t(8847).A+"",width:"742",height:"291"})}),"\n",(0,s.jsx)(n.p,{children:"Keep in mind your sketch polygon examples are randomly generated so your numbers will vary from the ones shown."}),"\n",(0,s.jsx)(n.p,{children:'And when viewing a sketch collection example, it should display the additional "Show By Sketch" list:'}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CoralReefCard collection view",src:t(97287).A+"",width:"748",height:"928"})}),"\n",(0,s.jsx)(n.h3,{id:"add-to-tab-report",children:"Add to Tab Report"}),"\n",(0,s.jsx)(n.p,{children:"Now add the CoralReefCard to a new page in your top-level TabReport."}),"\n",(0,s.jsxs)(n.p,{children:["Open ",(0,s.jsx)(n.code,{children:"src/clients/TabReport.tsx"})," and replace the code with the following:"]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/clients/TabReport.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-javascript",children:'import React, { useState } from "react";\nimport { useTranslation } from "react-i18next";\nimport {\n SegmentControl,\n ReportPage,\n SketchAttributesCard,\n} from "@seasketch/geoprocessing/client-ui";\nimport Translator from "../components/TranslatorAsync.js";\nimport { SimpleCard } from "../components/SimpleCard.js";\nimport { CoralReefCard } from "../components/CoralReefCard.js";\n\nconst BaseReport = () => {\n const { t } = useTranslation();\n const segments = [\n { id: "OVERVIEW", label: t("Overview") },\n { id: "BIOLOGICAL", label: t("Biological") },\n ];\n const [tab, setTab] = useState < string > "OVERVIEW";\n\n return (\n <>\n
\n setTab(segment)}\n segments={segments}\n />\n
\n \n \n \n \n \n \n \n >\n );\n};\n\n// Named export loaded by storybook\nexport const TabReport = () => {\n return (\n \n \n \n );\n};\n\n// Default export lazy-loaded by production ReportApp\nexport default TabReport;\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Storybook should update on save and display the following:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CoralReefCard add to page",src:t(62848).A+"",width:"1453",height:"823"})}),"\n",(0,s.jsx)(n.p,{children:"You should be able to click the tabs and switch between report pages."}),"\n",(0,s.jsx)(n.h2,{id:"benthic-habitat-report",children:"Benthic Habitat Report"}),"\n",(0,s.jsx)(n.p,{children:"Next you will create a report summarizing sketch overlap with 3 classes of rocky substrate (rock, rubble, sand) in the benthic zone (seabottom). Here is an image of it displayed in QGIS within the Micronesian EEZ boundary. Similar to the coral reefs, notice that these 3 types of rocky seabottom are mostly in shallower water near the islands and atolls."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Benthic habitat map",src:t(13065).A+"",width:"1345",height:"688"})}),"\n",(0,s.jsx)(n.h3,{id:"import-data-1",children:"Import Data"}),"\n",(0,s.jsx)(n.p,{children:"First, import the data."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run import:data\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"? Type of data?\nVector\n? Enter path to src file (with filename)\ndata/src/benthic-rock.fgb\n? Select layer to import\nbenthic-rock\n? Choose unique datasource name (a-z, A-Z, 0-9, -, _), defaults to filename\nbenthic-rock\n? Should multi-part geometries be split into single-part geometries?\nYes\n? (Optional) additional formats to create (besides fgb)\n[Press Enter to skip]\n? Select feature properties that you want to group metrics by\nclass\n? Select additional feature properties to keep in final datasource\n[Press Enter to skip]\n\nAdding benthic-rock record in project/datasources.json file\n"})}),"\n",(0,s.jsx)(n.h3,{id:"precalc-data-1",children:"Precalc Data"}),"\n",(0,s.jsxs)(n.p,{children:["Before you can use your benthic report, you need to precalculate the area of your benthic polygons. Rather than writing your own script for this, as was done for coral reefs, the ",(0,s.jsx)(n.code,{children:"precalc:data"})," command is available that will inspect your vector datasource and precalculate basic summary metrics (total feature area, total feature count, etc). Let's look at the datasource record generated for our benthic-rock datasource to understand what precalc will do."]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"project/datasources.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'{\n "src": "data/src/benthic-rock.fgb",\n "layerName": "benthic-rock",\n "geo_type": "vector",\n "datasourceId": "benthic-rock",\n "formats": [\n "fgb"\n ],\n "classKeys": [\n "class"\n ],\n "created": "2024-11-28T05:58:26.284Z",\n "lastUpdated": "2024-11-28T05:58:26.284Z",\n "propertiesToKeep": [\n "class"\n ],\n "explodeMulti": true,\n "precalc": true\n}\n'})})]}),"\n",(0,s.jsxs)(n.p,{children:["You'll notice that the ",(0,s.jsx)(n.code,{children:"precalc"})," property is set to true. That means that it is made available for precalculation. You can disable precalculation for any datasource you want at any time by setting it to ",(0,s.jsx)(n.code,{children:"false"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["You'll also notice that the ",(0,s.jsx)(n.code,{children:"class"})," attribute is configured under ",(0,s.jsx)(n.code,{children:"classKeys"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'"classKeys": [\n "class"\n],\n'})}),"\n",(0,s.jsxs)(n.p,{children:["This is because when importing your datasource, when asked to select feature properties that you want to group metrics by, you selected ",(0,s.jsx)(n.code,{children:"class"}),". If present, the precalc command will use this to precalculate metrics by each unique value present in the dataset for the ",(0,s.jsx)(n.code,{children:"class"})," attribute. If not present, you can simply add it now and save your file."]}),"\n",(0,s.jsx)(n.p,{children:"You're now ready to precalculate your metrics."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run precalc:data\n\n? Do you want to precalculate only a subset?\nNo, just precalculate everything (may take a while)\n\n...\n\n2 datasource/geography combinations precalculated successfully\n2 datasource/geography combinations skipped due to precalc disabled\n"})}),"\n",(0,s.jsxs)(n.p,{children:["You should now have precalculated ",(0,s.jsx)(n.code,{children:"area"})," and ",(0,s.jsx)(n.code,{children:"count"})," metrics for both reefextent and benthic-rock datasources. Let's look closer at the output."]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"project/precalc.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'[\n {\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Rock",\n "sketchId": null,\n "groupId": null,\n "value": 16604057.106034255\n },\n {\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Rubble",\n "sketchId": null,\n "groupId": null,\n "value": 14568314.003883593\n },\n {\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Sand",\n "sketchId": null,\n "groupId": null,\n "value": 41378302.21403051\n },\n {\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-total",\n "sketchId": null,\n "groupId": null,\n "value": 72550673.32394843\n },\n {\n "geographyId": "world",\n "metricId": "area",\n "classId": "reefextent-total",\n "sketchId": null,\n "groupId": null,\n "value": 716231422.607066\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "benthic-rock-Rock",\n "sketchId": null,\n "groupId": null,\n "value": 2712\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "benthic-rock-Rubble",\n "sketchId": null,\n "groupId": null,\n "value": 2002\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "benthic-rock-Sand",\n "sketchId": null,\n "groupId": null,\n "value": 2658\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "benthic-rock-total",\n "sketchId": null,\n "groupId": null,\n "value": 7372\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "reefextent-total",\n "sketchId": null,\n "groupId": null,\n "value": 14406\n }\n]\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Within this array of precalc metric records you will see four that represent the total area of all benthic-rock polygons and the total area for each of the 3 benthic rock classes:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-total",\n "sketchId": null,\n "groupId": null,\n "value": 72550673.32394843\n},\n{\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Rock",\n "sketchId": null,\n "groupId": null,\n "value": 16604057.106034255\n},\n{\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Rubble",\n "sketchId": null,\n "groupId": null,\n "value": 14568314.003883593\n},\n{\n "geographyId": "world",\n "metricId": "area",\n "classId": "benthic-rock-Sand",\n "sketchId": null,\n "groupId": null,\n "value": 41378302.21403051\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"These will get loaded and used by the BenthicReefCard as the denominator value when calculating percent sketch overlap."}),"\n",(0,s.jsx)(n.admonition,{type:"note",children:(0,s.jsxs)(n.p,{children:["If at any point the process of using ",(0,s.jsx)(n.code,{children:"import:data"}),", ",(0,s.jsx)(n.code,{children:"precalc:data"})," or the ",(0,s.jsx)(n.code,{children:"projectClient"})," don't meet your needs, you are welcome to create your own separate workflow. As long as datasources get to the ",(0,s.jsx)(n.code,{children:"data/dist"})," directory for publishing, in the format (fgb, cog) and projection required (EPSG 4326 for vector, EPSG 6933 for raster) you can create your own solution."]})}),"\n",(0,s.jsx)(n.h3,{id:"world-geography",children:"World Geography"}),"\n",(0,s.jsxs)(n.p,{children:["You might have noticed in the precalculated metrics that they are assigned a geographyId of ",(0,s.jsx)(n.code,{children:"world"}),". ",(0,s.jsx)(n.code,{children:"Geographies"})," are a higher level feature of the framework that define polygon boundaries that serve a specfic purpose in your project. The main use case is to define planning boundaries for your project, if you have them."]}),"\n",(0,s.jsxs)(n.p,{children:["The default Geography for a new project is the ",(0,s.jsx)(n.code,{children:"world"})," geography, which establishes the entire world as your planning boundary. This is sufficient for your needs until you have a more specific planning boundary that you want to work with. For example you can clip your sketches and your data to a geography in order to report metrics for a specific geography. Since your data is already pre-clipped to the planning area, and there is only one planning area, you don't need to do anything more with this feature. You can just leave it to use the ",(0,s.jsx)(n.code,{children:"world"})," geography."]}),"\n",(0,s.jsxs)(n.p,{children:["Geographies are defined in ",(0,s.jsx)(n.code,{children:"project/geographies.json"}),". To learn more visit the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/concepts#geographies",children:"advanced concepts"})," page."]}),"\n",(0,s.jsx)(n.h3,{id:"add-metric-group",children:"Add Metric Group"}),"\n",(0,s.jsxs)(n.p,{children:["A metric group is a higher-level entity that defines a metric to be measured, for one or more classes of data. ",(0,s.jsx)(n.code,{children:"MetricGroup"})," ",(0,s.jsx)(n.strong,{children:"records"})," can defined in ",(0,s.jsx)(n.code,{children:"project/metrics.json"})," and accessed using the project client in your geoprocessing functions and reports."]}),"\n",(0,s.jsxs)(n.p,{children:["Let's create a metric group by first looking at the benthic dataset. It represents where multiple classes of benthic habitat are present - sand, rock, rubble. Each polygon is assigned with a single habitat type using the ",(0,s.jsx)(n.code,{children:"class"})," attribute and given a value of ",(0,s.jsx)(n.code,{children:"Sand"}),", ",(0,s.jsx)(n.code,{children:"Rock"}),", or ",(0,s.jsx)(n.code,{children:"Rubble"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Add the following metric group object to ",(0,s.jsx)(n.code,{children:"project/metrics.json"})," and save the file."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "metricId": "benthicHabitat",\n "classKey": "class",\n "datasourceId": "benthic-rock",\n "classes": [\n {\n "classId": "Sand",\n "display": "Sand"\n },\n {\n "classId": "Rock",\n "display": "Rock"\n },\n {\n "classId": "Rubble",\n "display": "Rubble"\n }\n ]\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["This defines a ",(0,s.jsx)(n.code,{children:"benthicHabitat"})," metric that sources data from the ",(0,s.jsx)(n.code,{children:"benthic"})," datasource. The ",(0,s.jsx)(n.code,{children:"classKey"})," indicates this datasource has an attribute named ",(0,s.jsx)(n.code,{children:"class"})," used to identify which data class each polygon is a member of. 3 data classes are defined with a ",(0,s.jsx)(n.code,{children:"classId"})," serving as the unique identifier for the data class, and it also matches the value used in the data at the ",(0,s.jsx)(n.code,{children:"classKey"})," attribute."]}),"\n",(0,s.jsxs)(n.p,{children:["To learn more about metric groups, visit the ",(0,s.jsx)(n.a,{href:"/geoprocessing/docs/next/concepts#metric-group",children:"advanced concepts"})," page."]}),"\n",(0,s.jsx)(n.h3,{id:"create-report-1",children:"Create Report"}),"\n",(0,s.jsx)(n.p,{children:"Next you will create a report that uses your metric group. Run the following command and answer the questions:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run create:report\n"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"? Type of report to create\nVector overlap report - calculates sketch overlap with vector datasources\n? Describe what this reports geoprocessing function will calculate (e.g. Calculate sketch overlap with boundary polygons)\nCalculate sketch overlap with benthic habitat\n? Select the metric group to report on\nbenthicHabitat\n\n\u2714 Created benthicHabitat report\n\u2714 Registered report assets in project/geoprocessing.json\n\nGeoprocessing function: src/functions/benthicHabitat.ts\nSmoke test: src/functions/benthicHabitatSmoke.test.ts\nReport component: src/components/BenthicHabitatCard.tsx\nStory generator: src/components/BenthicHabitatCard.example-stories.ts\n\nNext Steps:\n * 'npm test' to run smoke tests against your new geoprocessing function\n * 'npm run storybook' to view your new report with smoke test output\n * Add to a top-level report client or page when ready\n"})}),"\n",(0,s.jsx)(n.p,{children:"You should now have a geoprocessing function and card component ready to go that will iterate through your data classes and calculate/report area overlap with your sketch."}),"\n",(0,s.jsx)(n.h3,{id:"test-new-example-sketch",children:"Test New Example Sketch"}),"\n",(0,s.jsxs)(n.p,{children:["Now run ",(0,s.jsx)(n.code,{children:"npm run test"})," again look at the new smoke test output for your geoprocessing function in ",(0,s.jsx)(n.code,{children:"examples/output"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"It's very likely that none of your random sketchs overlapped with any benthic polygons and all display zero. Add the following example sketch that we know will overlap."}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"examples/sketches/sketch2.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "type": "Feature",\n "properties": {\n "id": "78f6e916-20f0-471e-a15e-6d632650cf68",\n "isCollection": false,\n "userAttributes": [\n {\n "label": "Type",\n "fieldType": "ChoiceField",\n "exportId": "TYPE",\n "value": "sketch"\n },\n {\n "label": "Notes",\n "value": "NOTES",\n "fieldType": "TextArea"\n }\n ],\n "sketchClassId": "3ac026ad-c3eb-471a-b6ad-58782aa5e949",\n "createdAt": "2024-11-26T02:48:33.985Z",\n "updatedAt": "2024-11-26T02:48:33.985Z",\n "name": "sketch2"\n },\n "geometry": {\n "type": "Polygon",\n "coordinates": [\n [\n [151.31665625673213, 7.749571426060996],\n [151.31665625673213, 5.925462431466443],\n [153.9861009666032, 5.925462431466443],\n [153.9861009666032, 7.749571426060996],\n [151.31665625673213, 7.749571426060996]\n ]\n ]\n },\n "id": "78f6e916-20f0-471e-a15e-6d632650cf68"\n}\n'})})]}),"\n",(0,s.jsxs)(n.p,{children:["Now ",(0,s.jsx)(n.code,{children:"npm run test"})," and you should now see non-zero output for each benthic class for the sketch2 example:"]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"examples/output/sketch2/benthicHabitat.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "metrics": [\n {\n "geographyId": "world",\n "metricId": "benthicHabitat",\n "classId": "Rock",\n "sketchId": "78f6e916-20f0-471e-a15e-6d632650cf68",\n "groupId": null,\n "value": 11210186.968081,\n "extra": {\n "sketchName": "sketch2"\n }\n },\n {\n "geographyId": "world",\n "metricId": "benthicHabitat",\n "classId": "Rubble",\n "sketchId": "78f6e916-20f0-471e-a15e-6d632650cf68",\n "groupId": null,\n "value": 11210186.968081,\n "extra": {\n "sketchName": "sketch2"\n }\n },\n {\n "geographyId": "world",\n "metricId": "benthicHabitat",\n "classId": "Sand",\n "sketchId": "78f6e916-20f0-471e-a15e-6d632650cf68",\n "groupId": null,\n "value": 11210186.968081,\n "extra": {\n "sketchName": "sketch2"\n }\n }\n ]\n}\n'})})]}),"\n",(0,s.jsx)(n.h3,{id:"add-to-tab-report-1",children:"Add To Tab Report"}),"\n",(0,s.jsxs)(n.p,{children:["Next, add BenthicHabitatCard to a new ",(0,s.jsx)(n.strong,{children:"Habitat"})," page in TabReport. Open ",(0,s.jsx)(n.code,{children:"src/clients/TabReport.tsx"})," and replace the code with the following:"]}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/clients/TabReport.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-javascript",children:'import React, { useState } from "react";\nimport { useTranslation } from "react-i18next";\nimport {\n SegmentControl,\n ReportPage,\n SketchAttributesCard,\n} from "@seasketch/geoprocessing/client-ui";\nimport Translator from "../components/TranslatorAsync.js";\nimport { SimpleCard } from "../components/SimpleCard.js";\nimport { CoralReefCard } from "../components/CoralReefCard.js";\nimport { BenthicHabitatCard } from "../components/BenthicHabitatCard.js";\n\nconst BaseReport = () => {\n const { t } = useTranslation();\n const segments = [\n { id: "OVERVIEW", label: t("Overview") },\n { id: "BIOLOGICAL", label: t("Biological") },\n { id: "HABITAT", label: t("Habitat") },\n ];\n const [tab, setTab] = useState < string > "OVERVIEW";\n\n return (\n <>\n
\n setTab(segment)}\n segments={segments}\n />\n
\n \n \n \n \n \n \n \n \n \n \n >\n );\n};\n\n// Named export loaded by storybook\nexport const TabReport = () => {\n return (\n \n \n \n );\n};\n\n// Default export lazy-loaded by production ReportApp\nexport default TabReport;\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Storybook should update on save and display the following:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"CoralReefCard add to page",src:t(2187).A+"",width:"1464",height:"1299"})}),"\n",(0,s.jsx)(n.h2,{id:"seamount-report",children:"Seamount Report"}),"\n",(0,s.jsx)(n.p,{children:"Next you will create a report summarizing sketch overlap with areas that are within 40k kilometers of a seamount, which is an underwater mountain that rises at least 1,000 meters above the surrounding ocean. Here is an image of these areas displayed in QGIS within the Micronesian EEZ boundary."}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Seamount",src:t(37720).A+"",width:"1080",height:"607"})}),"\n",(0,s.jsx)(n.p,{children:"The seamount dataset is in a raster format. It is a binary raster such that each raster cell has a value of zero or one. Rasters are like digital images, in that each pixel or cell represents a specific rectangular area of the world and gives it a value. This particular dataset is a binary raster. Each cell has a value of zero or one. A one value indicates that the cell is within 40 kilemeters of a seamount, a zero value indicates it is not."}),"\n",(0,s.jsx)(n.h3,{id:"import-data-2",children:"Import Data"}),"\n",(0,s.jsx)(n.p,{children:"Next you will import this seamount raster:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run import:data\n\n? Type of data?\nRaster\n\n? Enter path to src file (with filename)\ndata/src/seamounts_40km.tif\n\n? Choose unique datasource name (a-z, A-Z, 0-9, -, _), defaults to filename\nseamounts_40km\n\n? Select raster band to import\n1\n\n? What type of measurement is used for this raster data?\nQuantitative - cell value (number) represents a measurement of a single thing\n\nAdding seamounts_40km record in project/datasources.json file\n"})}),"\n",(0,s.jsx)(n.h3,{id:"precalc-data-2",children:"Precalc Data"}),"\n",(0,s.jsxs)(n.p,{children:["Now, precalculating metrics for a cloud-optimized geotiff raster is a little more complicated than for a flatgeobuf. For this reason, we want to make use of the built-in ",(0,s.jsx)(n.code,{children:"precalc"})," feature. Run the precalc command as follow:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm run precalc:data\n\n? Do you want to precalculate only a subset?\nYes, by datasource\n\n? Which datasources do you want to precalculate? (will precalculate for all geographies)\nLet me choose\n\n? What datasources would you like to precalculate? (select as many as you want)\nseamounts_40km - raster\n\nPrecalculating datasource seamounts_40km for geography world\n1 datasource/geography combinations precalculated successfully\n"})}),"\n",(0,s.jsx)(n.p,{children:"Now look at project/precalc.json. You should see 4 new precalculated metrics for octocorals:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"valid"})," - count of all raster cells with value (not nodata cells)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"count"})," - count of all cells in the raster, both valid and invalid (nodata)"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"sum"})," - sum of value of all valid cell values in raster"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"area"})," - area of valid cells in raster in square meters"]}),"\n"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:' {\n "geographyId": "world",\n "metricId": "area",\n "classId": "seamounts_40km-total",\n "sketchId": null,\n "groupId": "band-0",\n "value": 400949272332.4638\n },\n {\n "geographyId": "world",\n "metricId": "count",\n "classId": "seamounts_40km-total",\n "sketchId": null,\n "groupId": "band-0",\n "value": 18748\n },\n {\n "geographyId": "world",\n "metricId": "sum",\n "classId": "seamounts_40km-total",\n "sketchId": null,\n "groupId": "band-0",\n "value": 1365\n },\n {\n "geographyId": "world",\n "metricId": "valid",\n "classId": "seamounts_40km-total",\n "sketchId": null,\n "groupId": "band-0",\n "value": 1365\n }\n'})}),"\n",(0,s.jsx)(n.p,{children:"The area calculation is made possible by the fact that the raster is in an equal area projection, making all raster cells a consistent size. Area is calculated as:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"area = raster cell width in meters x cell height in meters x number of valid cells"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Notice that the precalculated ",(0,s.jsx)(n.code,{children:"sum"})," and ",(0,s.jsx)(n.code,{children:"valid"})," values are the same at ",(0,s.jsx)(n.code,{children:"1365"}),". That is because the valid cells all have a value of 1 and the sum of the values in valid cells is the same as the count of valid cells."]}),"\n",(0,s.jsx)(n.h3,{id:"add-objective",children:"Add Objective"}),"\n",(0,s.jsxs)(n.p,{children:["You will also use the built-in framework support for objectives. It allows you to configure a target value and measure progress toward it in a report. Open ",(0,s.jsx)(n.code,{children:"project/objectives.json"})," and add the following objective:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'[\n {\n "objectiveId": "seamounts",\n "shortDesc": "Seamounts 30%",\n "target": 0.3,\n "countsToward": {}\n }\n]\n'})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"countsToward"})," property isn't necessary for this sample project but it allows you to indicate which of one or more categories count towards meeting the target. For example if you allow a user to assign a protection level to their sketch, you can allow only the two highest levels of protection to count toward meeting the target."]}),"\n",(0,s.jsx)(n.p,{children:"Example (do not add):"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "countsToward": {\n "Full Protection": "yes",\n "High Protection": "yes",\n "Low Protection": "no"\n }\n}\n'})}),"\n",(0,s.jsx)(n.h3,{id:"add-metric-group-1",children:"Add Metric Group"}),"\n",(0,s.jsx)(n.p,{children:"The last bit of preparation is you will create a metric group. This will allow you to easily access your precalc metrics and your objective in your report card."}),"\n",(0,s.jsxs)(n.p,{children:["Create a seamount metric group that uses the objective in ",(0,s.jsx)(n.code,{children:"project/metrics.json"}),"."]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n "metricId": "seamounts",\n "datasourceId": "seamounts_40km",\n "classes": [\n {\n "classId": "seamounts",\n "display": "Seamounts",\n "objectiveId": "seamounts"\n }\n ]\n}\n'})}),"\n",(0,s.jsx)(n.h3,{id:"create-report-2",children:"Create Report"}),"\n",(0,s.jsx)(n.p,{children:"Now create a blank seamount report"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:"npm run create:report\n\n? Type of report to create\nBlank report - empty report ready to build from scratch\n\n? Describe what this reports geoprocessing function will calculate (e.g. Calculate sketch overlap with boundary polygons)\nCalculate sketch overlap with seamount raster\n\n? Title for this report, in camelCase\nseamounts\n\n\u2714 Created seamounts report\n\u2714 Registered report assets in project/geoprocessing.json\n\nGeoprocessing function: src/functions/seamounts.ts\nSmoke test: src/functions/seamountsSmoke.test.ts\nReport component: src/components/SeamountsCard.tsx\nStory generator: src/components/SeamountsCard.example-stories.ts\n\nNext Steps:\n * 'npm test' to run smoke tests against your new geoprocessing function\n * 'npm run storybook' to view your new report with smoke test output\n * Add to a top-level report client or page when ready\n"})}),"\n",(0,s.jsxs)(n.p,{children:["This creates both a geoprocessing function and report card, and registers them in ",(0,s.jsx)(n.code,{children:"project/geoprocessing.json"}),"."]}),"\n",(0,s.jsxs)(n.p,{children:["Open ",(0,s.jsx)(n.code,{children:"src/functions/seamounts.ts"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"You will now update this code answer the following questions:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"What percentage of area within 40 kilometers of a seamount is within the current sketch polygon (or sketch collection polygons)?"}),"\n",(0,s.jsx)(n.li,{children:"If it is a sketch collection, does it meet the planning objective of protecting 30% of all area within 40 kilometers of a seamount?"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Replace the existing code with the following:"}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/functions/seamounts.ts"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'import {\n Sketch,\n SketchCollection,\n Polygon,\n MultiPolygon,\n GeoprocessingHandler,\n DefaultExtraParams,\n loadCog,\n rasterStats,\n toSketchArray,\n StatsObject,\n} from "@seasketch/geoprocessing";\nimport project from "../../project/projectClient.js";\n\nexport interface SeamountResult {\n /** Sum of valid seamount raster cells overlapping with sketch */\n stats: StatsObject[];\n childSketchStats: {\n /** Name of the sketch */\n name: string;\n /** Sum of valid seamount raster cells overlapping with sketch */\n stats: StatsObject[];\n }[];\n}\n\n/**\n * seamounts for use with create:report command\n */\nexport async function seamounts(\n sketch:\n | Sketch\n | SketchCollection,\n extraParams: DefaultExtraParams = {},\n): Promise {\n const metricGroup = project.getMetricGroup("seamounts");\n const ds = project.getMetricGroupDatasource(metricGroup);\n const url = project.getDatasourceUrl(ds);\n const raster = await loadCog(url);\n\n // Add analysis code\n const stats = await rasterStats(raster, {\n feature: sketch,\n stats: ["sum"],\n });\n\n let childSketchStats: SeamountResult["childSketchStats"] = [];\n if (sketch.properties.isCollection) {\n childSketchStats = await Promise.all(\n toSketchArray(sketch).map(async (childSketch) => {\n const childStats = await rasterStats(raster, {\n feature: childSketch,\n stats: ["sum"],\n });\n return {\n name: childSketch.properties.name,\n stats: childStats,\n };\n }),\n );\n }\n\n // Custom return type\n return {\n stats,\n childSketchStats,\n };\n}\n\nexport default new GeoprocessingHandler(seamounts, {\n title: "seamounts",\n description: "Calculate sketch overlap with seamount data",\n timeout: 60, // seconds\n memory: 1024, // megabytes\n executionMode: "async",\n});\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Notice the more sophisticated result payload. It's designed to return one or more raster stats for the top-level sketch, and one or more child sketch stats if it's a sketch collection. This gives the structure some room to grow if you want to produce multiple raster stats for each sketch and use them in this report."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:"export interface SeamountResult {\n /** Sum of valid seamount raster cells overlapping with sketch */\n stats: StatsObject[];\n childSketchStats: {\n /** Name of the sketch */\n name: string;\n /** Sum of valid seamount raster cells overlapping with sketch */\n stats: StatsObject[];\n }[];\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Then it fetches the metadata for the seamounts raster, ready to read data from it."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'const metricGroup = project.getMetricGroup("seamounts");\nconst ds = project.getMetricGroupDatasource(metricGroup);\nconst url = project.getDatasourceUrl(ds);\nconst raster = await loadCog(url);\n'})}),"\n",(0,s.jsx)(n.p,{children:"Now let's look at the analysis code. If it's a sketch collection, sum the value of all rasters cells that overlap with each child sketch."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// If sketch is collection, sum the value of raster cells that overlap with each child sketch\nlet childSketchStats: SeamountResult["childSketchStats"] = [];\nif (sketch.properties.isCollection) {\n childSketchStats = await Promise.all(\n toSketchArray(sketch).map(async (childSketch) => {\n const childStats = await rasterStats(raster, {\n feature: childSketch,\n stats: ["sum"],\n });\n return {\n name: childSketch.properties.name,\n stats: childStats,\n };\n }),\n );\n}\n'})}),"\n",(0,s.jsx)(n.p,{children:"Next, sum the value of raster cells that overlap the entire top-level sketch or sketch collection and return the final result payload."}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-typescript",children:'// Calculate overall sketch area\nconst stats = await rasterStats(raster, {\n feature: sketch,\n stats: ["sum"],\n});\n\n// Custom return type\nreturn {\n stats,\n childSketchStats,\n};\n'})}),"\n",(0,s.jsx)(n.p,{children:"If this is a sketch collection, you might notice that an optimization would be to sum the value of all the child sketches to get the overall sum for the whole collection. That is true, if your sketches are guaranteed not to overlap. In practice, sketches often can and do overlap in a planning process. The planning process may even allow it such as areas of higher protection within areas of lower protection. This is an optimization left to you."}),"\n",(0,s.jsx)(n.p,{children:"Now run tests"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-bash",children:"npm test\n"})}),"\n",(0,s.jsx)(n.p,{children:"Confirm that the output looks as expected."}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"examples/output/sketchCollection1/seamounts.json"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-text",children:'{\n "stats": [\n {\n "sum": 5\n }\n ],\n "childSketchStats": [\n {\n "name": "sketchCollection1-1",\n "stats": [\n {\n "sum": 1\n }\n ]\n },\n {\n "name": "sketchCollection1-2",\n "stats": [\n {\n "sum": 1\n }\n ]\n },\n {\n "name": "sketchCollection1-3",\n "stats": [\n {\n "sum": 0\n }\n ]\n },\n {\n "name": "sketchCollection1-4",\n "stats": [\n {\n "sum": 0\n }\n ]\n },\n {\n "name": "sketchCollection1-5",\n "stats": [\n {\n "sum": 1\n }\n ]\n },\n {\n "name": "sketchCollection1-6",\n "stats": [\n {\n "sum": 0\n }\n ]\n },\n {\n "name": "sketchCollection1-7",\n "stats": [\n {\n "sum": 2\n }\n ]\n },\n {\n "name": "sketchCollection1-8",\n "stats": [\n {\n "sum": 0\n }\n ]\n },\n {\n "name": "sketchCollection1-9",\n "stats": [\n {\n "sum": 0\n }\n ]\n },\n {\n "name": "sketchCollection1-10",\n "stats": [\n {\n "sum": 0\n }\n ]\n }\n ]\n}\n'})})]}),"\n",(0,s.jsx)(n.p,{children:"Now, open src/components/SeamountsCard.tsx."}),"\n",(0,s.jsx)(n.p,{children:"You will now update this code to:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Display the % of total area within 40 kilometers of a seamount captured within this sketch"}),"\n",(0,s.jsxs)(n.li,{children:["If it is a sketch collection","\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"Indicate whether the objective of protecting 30% of all area within 40 kilometers of a seamount has been met."}),"\n",(0,s.jsx)(n.li,{children:"Display a collapsible area with a breakdown of the area and % area of seamount within each individual sketch in the collection."}),"\n"]}),"\n"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Replace the existing code with the following:"}),"\n",(0,s.jsxs)(a,{children:[(0,s.jsx)("summary",{children:"src/components/SeamountCard.tsx"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-javascript",children:'import React from "react";\nimport { Trans, useTranslation } from "react-i18next";\nimport {\n Collapse,\n HorizontalStackedBar,\n ObjectiveStatus,\n ResultsCard,\n Table,\n useSketchProperties,\n VerticalSpacer,\n} from "@seasketch/geoprocessing/client-ui";\nimport {\n percentWithEdge,\n roundDecimalFormat,\n squareMeterToKilometer,\n} from "@seasketch/geoprocessing/client-core";\nimport project from "../../project/projectClient.js";\nimport { SeamountResult } from "../functions/seamounts.js";\n\nexport const SeamountsCard = () => {\n const { t } = useTranslation();\n const [{ isCollection }] = useSketchProperties();\n const titleTrans = t("SeamountCard title", "SeamountCard");\n\n // Get precalc total sum\n const curGeography = project.getGeographyById("world", {\n fallbackGroup: "default-boundary",\n });\n const metricGroup = project.getMetricGroup("seamounts", t);\n const precalcMetrics = project.getPrecalcMetrics(\n metricGroup,\n "sum",\n curGeography.geographyId,\n );\n const sumTotal = precalcMetrics[0].value;\n\n // Get objective target\n const target = project.getObjectiveById("seamounts").target;\n\n return (\n <>\n \n {(data: SeamountResult) => {\n console.log("precalc", precalcMetrics);\n console.log("data", data);\n\n const sumPerc = data.stats[0].sum! / sumTotal;\n const sumPercString = percentWithEdge(sumPerc);\n const targetPercString = percentWithEdge(target);\n\n const meetsObjective = sumPerc >= target;\n\n // Adjust values for chart to be in range 0-100\n const chartRows = [[[sumPerc * 100]]];\n\n const sketchStr = isCollection ? t("sketch collection") : t("sketch");\n\n const meetsOrNotElement = meetsObjective ? (\n \n This {{ sketchStr }} meets the objective of protecting{" "}\n {{ targetPercString }} of area within 40 km of a seamount.\n \n ) : (\n \n This {{ sketchStr }} does not meet the objective of protecting{" "}\n {{ targetPercString }} of area within 40 km of a seamount.\n \n );\n\n return (\n <>\n
\n \n {{ sumPercString }} of all areas within 40 kilometers of a\n seamount is within this {{ sketchStr }}.\n \n