{"allowedRenditionsWidth":["320","480","640","768","960","1200","1440","1920"],"cssClassNames":"page basicpage summit-page","language":"en","description":"Apply geospatial features to ML models with Streamlit and Snowflake for location-aware predictions, proximity analysis, and spatial clustering.","title":"Geospatial Analytics, AI and ML using Snowflake","templateName":"quickstart-page-template","analyticsPageType":"quickstart-page-template","analyticsCategory":"general","analyticsSubCategory":"","excludeFromAnalytics":false,"isPasswordProtected":false,"analyticsContentTags":["snowflake-site:taxonomy/product/analytics","snowflake-site:taxonomy/snowflake-feature/unstructured-data-analysis","snowflake-site:taxonomy/solution-center/certification/quickstart"],"analyticsEnabled":true,"coveoConfig":{"organizationId":"snowflakecomputingproduction8neljofn","apiKey":"xx335921a6-2a0a-40f2-a167-e390b4766c3d","pipeline":"snowflake.com","searchHub":"snowflake.com"},"analyticsDebugMode":false,"analyticsData":{"excludeFromAnalytics":false,"subCategory":"","pageType":"quickstart-page-template","templateName":"quickstart-page-template","siteName":"snowflake","pageUrl":"/content/snowflake-site/global/en/developers/guides/geo-for-machine-learning","language":"en","category":"general","pageName":"Geospatial Analytics, AI and ML using Snowflake","contentTags":["snowflake-site:taxonomy/product/analytics","snowflake-site:taxonomy/snowflake-feature/unstructured-data-analysis","snowflake-site:taxonomy/solution-center/certification/quickstart"]},":mappedPath":"/en/developers/guides/geo-for-machine-learning/",":type":"snowflake-site/components/structure/page",":items":{"root":{"columnClassNames":{"markup_editor_1950346551":"aem-GridColumn aem-GridColumn--default--12","experiencefragment-banner":"aem-GridColumn aem-GridColumn--default--12","experiencefragment-header":"aem-GridColumn aem-GridColumn--default--12","responsivegrid":"aem-GridColumn aem-GridColumn--default--12","experiencefragment-footer":"aem-GridColumn aem-GridColumn--default--12","modal_container":"aem-GridColumn aem-GridColumn--default--12","markup_editor":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnCount":12,":items":{"experiencefragment-banner":{"id":"experiencefragment-1544552a1b","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/pushdown-banner/master/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment",":items":{"root":{"columnClassNames":{"pushdown_banner_copy":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-ccd35443c2",":type":"snowflake-site/components/container",":items":{"pushdown_banner_copy":{"id":"pushdown-banner-b05c1ecf24","contentHeadline":"Snowflake World Tour hits your city","contentDescription":"See how leading teams deploy agents at scale. Find a stop near you. Register free.","contentJustifyContent":"center","linkStyle":"text-white","linkCTA":{"id":"link-cta","heapButtonClasses":["pushdown_banner"],"showOutboundIcon":false,"buttonLink":{"valid":true,"attributes":{"target":"_blank"},"url":"/en/world-tour/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Register now"},":type":"snowflake-site/components/pushdown-banner","appliedCssClassNames":"snowflake-pushdown-banner-text-white snowflake-pushdown-banner-background-black"}},":itemsOrder":["pushdown_banner_copy"]},"image":{":type":"nt:unstructured"},"cq:metadata":{":type":"nt:unstructured"}},":itemsOrder":["root","image","cq:metadata"],"classNames":"aem-xf"},"experiencefragment-header":{"id":"experiencefragment-3261c7f59a","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment",":items":{"root":{"columnClassNames":{"mega_header":"aem-GridColumn aem-GridColumn--default--12","markup_editor":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-b774dfe5c6",":type":"snowflake-site/components/container",":items":{"markup_editor":{"id":"markup-editor-f4f36489f8","title":" ","cssContent":".footer-nav__link-group .snowflake-button-container,.subnav__item--button,.snowflake-card-v2-advanced-button .snowflake-button-container{justify-content:flex-start}.mega-nav__sign-in.snowflake-button-container{display:none}@media screen and (min-width:768px){.mega-nav__sign-in.snowflake-button-container{display:inline-block;font-family:'Texta',sans-serif;font-weight:800 !important}}@media screen and (min-width:1024px) and (max-width:1199px){.snowflake-mega-nav-header-buttons-container .snowflake-button-blue .snowflake-button-container{font-size:13px !important}.snowflake-language-navigation .language-icon{width:18px !important;height:18px !important;margin-right:4px !important}}.mega-nav__sign-in svg{display:none}.nav-item__platform-parent-why-sf.snowflake-mega-nav-nav-item\u003Ea:hover,.nav-item__platform-parent.snowflake-mega-nav-nav-item\u003Ea:hover{background-color:transparent !important}.nav-platform-sidebar .snowflake-mega-nav-nav-item:hover.blue-icon .snowflake-mega-nav-nav-item-icon__inner{background-color:var(--ui-01) !important}@media screen and (min-width:1024px){.snowflake-mega-nav-navigation-dropdown{overflow:hidden}.meganav-platform-features{padding-left:64px}.meganav-platform-features::before{content:'';transform:translateX(-64px);display:block;z-index:0;width:100%;height:100%;position:absolute;top:0;background:#f7f9fa}.nav-item--si.snowflake-mega-nav-nav-item\u003Ea:hover{background-color:transparent}.nav-item--si{border-bottom:1px solid #ccc;padding-bottom:16px;margin-bottom:8px}.nav-item__platform-parent{border-bottom:1px solid #ccc;margin-bottom:8px;padding-bottom:16px}.nav-item__platform-parent-why-sf .snowflake-mega-nav-nav-item-description::after{content:'What Snowflake can do for you \u003E';display:block;color:var(--ui-01);margin-top:16px}.nav-item__platform-parent .snowflake-mega-nav-nav-item-description::after{content:'View the platform \u003E';display:block;color:var(--ui-01);margin-top:16px}}@media screen and (min-width:1367px){.snowflake-mega-nav-nav-item-description{font-size:13px !important;line-height:20px !important}.snowflake-mega-nav-nav-item-title-wrapper\u003E.snowflake-mega-nav-nav-item-title{font-size:17px !important}.nav-item__platform-parent-why-sf .snowflake-mega-nav-nav-item-title,.nav-item__platform-parent .snowflake-mega-nav-nav-item-title{font-size:24px !important;line-height:32px !important;margin-bottom:8px !important}.nav-item__platform-parent-why-sf .snowflake-mega-nav-nav-item-description,.nav-item__platform-parent .snowflake-mega-nav-nav-item-description{font-size:14px !important;line-height:20px !important}}html.wf-texta-n9-loading .display-1-v2{font-size:48px!important;line-height:50px!important;letter-spacing:-.5px!important;font-family:sans-serif!important}html.wf-texta-n9-loading .heading-4-v2{font-size:18px!important;line-height:24px!important;font-family:sans-serif!important}@media screen and (min-width:768px){html.wf-texta-n9-loading .display-2-v2{font-size:48px!important;line-height:50px!important;font-family:sans-serif!important}html.wf-texta-n9-loading .display-1-v2{font-size:55.5px!important;line-height:54px!important;letter-spacing:-.5px!important;font-family:sans-serif!important}html.wf-lato-n4-loading .body-2,html.wf-lato-n4-loading .heading-5-v2,html.wf-lato-n4-loading .snowflake-card-v2-advanced-text .snowflake-text p{font-size:15.5px!important;font-family:sans-serif!important}html.wf-texta-n9-loading .heading-2,html.wf-texta-n9-loading .heading-2-v2{font-size:34px!important;line-height:38px!important;letter-spacing:-.75px!important;font-family:sans-serif!important}html.wf-texta-n8-loading .heading-6-v2.snowflake-mega-nav-navigation-title{font-size:13.5px!important;font-family:sans-serif!important}html.wf-texta-n8-loading .heading-4,html.wf-texta-n8-loading .snowflake-button-container,html.wf-texta-n8-loading .snowflake-button-regular .snowflake-button-container{font-size:13px!important;line-height:20px!important;letter-spacing:.25px!important;font-family:sans-serif!important}}@media screen and (min-width:1024px){html.wf-lato-n4-loading .snowflake-mega-nav-nav-item-description{font-size:11.5px!important;font-family:sans-serif!important}html.wf-lato-n4-loading .body-2,html.wf-lato-n4-loading .text-size-regular .snowflake-text li,html.wf-lato-n4-loading .text-size-regular .snowflake-text p,html.wf-lato-n4-loading .text-size-regular .snowflake-text span[data-testid=text-content],html.wf-lato-n4-loading .text-size-regular.cq-Editable-dom li,html.wf-lato-n4-loading .text-size-regular.cq-Editable-dom p,html.wf-lato-n4-loading .text-size-regular.cq-Editable-dom span[data-testid=text-content]{font-size:13.5px!important;font-family:sans-serif!important}html.wf-texta-n8-loading .snowflake-button-compact .snowflake-button-container{font-size:12px!important;letter-spacing:0!important;line-height:18px!important}}@media screen and (min-width:1367px){html.wf-lato-n4-loading .hp-hero__eyebrow a\u003Eb:first-child{font-size:11px!important;font-family:sans-serif!important}html.wf-texta-n8-loading .hp-hero__eyebrow a{font-size:13px!important;font-family:sans-serif!important}html.wf-texta-n9-loading .display-2-v2{font-size:61px!important;line-height:60px!important;font-family:sans-serif!important}html.wf-texta-n9-loading .display-1-v2{font-size:74.5px!important;line-height:74px!important;letter-spacing:-.75px!important;font-family:sans-serif!important}html.wf-texta-n9-loading .heading-2,html.wf-texta-n9-loading .heading-2-v2{font-size:41px!important;letter-spacing:-.75px!important;font-family:sans-serif!important}html.wf-texta-n9-loading .heading-3-v2{font-family:sans-serif!important;letter-spacing:-.75px!important;font-size:33.75px!important}html.wf-texta-n9-loading .heading-4-v2{font-size:19.5px!important;line-height:26px!important;font-family:sans-serif!important}html.wf-texta-n8-loading .heading-6-v2{font-size:12px!important;font-family:sans-serif!important}html.wf-texta-n8-loading .heading-6-v2.snowflake-mega-nav-navigation-title{font-size:14px!important;font-family:sans-serif!important}html.wf-lato-n4-loading .body-1,html.wf-lato-n4-loading .cq-Editable-dom[data-cq-data-path*=text] ol\u003Eli,html.wf-lato-n4-loading .snowflake-text li,html.wf-lato-n4-loading .snowflake-text p,html.wf-lato-n4-loading .text-size-large .snowflake-text li,html.wf-lato-n4-loading .text-size-large .snowflake-text p,html.wf-lato-n4-loading .text-size-large .snowflake-text span[data-testid=text-content],html.wf-lato-n4-loading .text-size-large.cq-Editable-dom li,html.wf-lato-n4-loading .text-size-large.cq-Editable-dom p,html.wf-lato-n4-loading .text-size-large.cq-Editable-dom span[data-testid=text-content],html.wf-lato-n4-loading.cq-Editable-dom[data-cq-data-path*=text]\u003Ep,html.wf-lato-n4-loading.cq-Editable-dom[data-cq-data-path*=text]\u003Eul\u003Eli{font-size:17.5px!important;font-family:sans-serif!important}html.wf-lato-n4-loading .body-2,html.wf-lato-n4-loading .text-size-regular .snowflake-text li,html.wf-lato-n4-loading .text-size-regular .snowflake-text p,html.wf-lato-n4-loading .text-size-regular .snowflake-text span[data-testid=text-content],html.wf-lato-n4-loading .text-size-regular.cq-Editable-dom li,html.wf-lato-n4-loading .text-size-regular.cq-Editable-dom p,html.wf-lato-n4-loading .text-size-regular.cq-Editable-dom span[data-testid=text-content],html.wf-texta-n8-loading .snowflake-button-link .snowflake-button-container,html.wf-texta-n8-loading .snowflake-button-link-back .snowflake-button-container{font-size:15.5px!important;font-family:sans-serif!important}html.wf-lato-n4-loading .body-3,html.wf-lato-n4-loading .text-size-small .snowflake-text li,html.wf-lato-n4-loading .text-size-small .snowflake-text p,html.wf-lato-n4-loading .text-size-small .snowflake-text span[data-testid=text-content],html.wf-lato-n4-loading .text-size-small.cq-Editable-dom li,html.wf-lato-n4-loading .text-size-small.cq-Editable-dom p,html.wf-lato-n4-loading .text-size-small.cq-Editable-dom span[data-testid=text-content]{font-size:13.5px!important;font-family:sans-serif!important}}#industryPlatformSection,.sc-hero{background-position:top left;background-size:20% auto}.bwalignc,.bwalignr{list-style-position:inside}.snowflake-text p sup{font-size:10px}#industryPlatformSection .industry-platform__row .snowflake-flexible-column-container-items,.button-group-pair\u003E.container\u003E.cmp-container\u003E.aem-container,.snowflake-hero-system-content-container{gap:16px}.agenda-item p,.button-group-pair\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv,.partner-details p{margin:0!important}.button-group-pair\u003E.container\u003E.cmp-container\u003E.aem-container::after,.button-group-pair\u003E.container\u003E.cmp-container\u003E.aem-container::before,.hide-logo .snowflake-case-study-card-logo,.partner-page__powered-by-logo,.sc-hero div.code-toolbar\u003E.toolbar,.snowflake-card-v2-advanced.no-link .snowflake-card-v2-advanced-button,.snowflake-partner-hero-card-badge-container{display:none!important}.section--card-mobile-carousel .snowflake-flexible-column-container-items-with-carousel{max-width:100%!important}@media screen and (min-width:768px){.button-group-pair .snowflake-button-container.inline-button--desktop,.button-group-pair\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:auto!important;display:inline-block!important}.button-group-pair\u003E.container\u003E.cmp-container\u003E.aem-container{align-items:center;justify-content:flex-start!important}.button-group-pair.center\u003E.container\u003E.cmp-container\u003E.aem-container{justify-content:center!important}.section--card-mobile-carousel{margin-left:var(--tablet-portrait-margin,48px)!important;margin-right:var(--tablet-portrait-margin,48px);width:calc(100% - 96px)!important;width:calc(100% - var(--tablet-portrait-margin) * 2)!important}}@media screen and (min-width:1024px){.section--card-mobile-carousel{margin-left:var(--tablet-horizontal-margin,48px)!important;margin-right:var(--tablet-horizontal-margin,48px);width:calc(100% - 96px)!important;width:calc(100% - var(--tablet-horizontal-margin) * 2)!important}.snowflake-mega-nav-header-mobile-icon{display:none!important}}@media screen and (min-width:1367px){.section--card-mobile-carousel{margin-left:var(--desktop-margin,6.5%)!important;margin-right:var(--desktop-margin,6.5%);width:87%!important;width:calc(100% - var(--desktop-margin) * 2)!important}.logo-container{min-width:143px}.sc-hero__headline .heading-1-v2{font-size:60px}.snowflake-mega-nav-navigation-title{font-size:17px}.snowflake-mega-nav-dropdown-footer-wrapper .snowflake-title-v2 .snowflake-title-v2-line:first-child{font-size:16px!important;line-height:24px!important}}.hero--home{overflow:hidden;background-color:var(--ui-01);z-index:2}.hp-hero__subheadline{width:90%}.hero--home .snowflake-button-container{transition:.3s}.hero--home .snowflake-button-primary a:hover,.hero--home .snowflake-button-secondary a:hover,.hero--home .snowflake-button-white a:hover{transition:.3s;background-color:var(--ui-02)!important;color:var(--ui-05)!important}.hero--home .snowflake-button-secondary a:hover{border-color:var(--ui-05)!important}.hero--home .snowflake-button-primary a:hover,.hero--home .snowflake-button-white a:hover{border-color:var(--ui-02)!important}.bwalignc,.hp-hero__eyebrow{text-align:center}.hp-hero__eyebrow a{display:inline-flex;flex-direction:column;justify-content:center;cursor:pointer;padding:8px;border-radius:var(--spacing-01);gap:8px;align-items:center;background-color:#45aee3;color:var(--ui-03);font-family:Texta,sans-serif;font-weight:800;font-size:16px;line-height:22px;transition:background-color .3s}.hp-hero__eyebrow a:hover{background-color:#7fc6ea;text-decoration:none;transition:background-color .3s}.hp-hero__eyebrow a\u003Eb:first-child{text-transform:uppercase;white-space:nowrap;display:inline-block;background-color:var(--ui-02);color:var(--ui-05);font-size:12px!important;line-height:16px!important;font-family:Lato,sans-serif;font-weight:500!important;padding:3px 6px;border-radius:2px;letter-spacing:1px}@media screen and (min-width:767px){.hp-hero__eyebrow{text-align:left}.hp-hero__eyebrow a{flex-direction:row;text-align:left}}.hero--home__inner .offset-video,.hero--home__inner .snowflake-experience-fragment,.offset-video__bg-image{max-height:200px;overflow:hidden}.hero--home__inner .offset-video .wistia-responsive-padding{padding-top:100%}.hero--home__inner .snowflake-experience-fragment,.offset-video__bg-image{position:absolute!important;top:0;left:0;width:100%}.offset-video__bg-image{z-index:-1}@media screen and (min-width:768px){.hero--home__inner .snowflake-experience-fragment,.offset-video,.offset-video__bg-image{position:absolute!important;max-height:none;top:0;left:0;width:250%;padding-bottom:250%;transform:translate(0,-50%);height:0}.workloads_7.unistore{max-width:317px}}.promo-banner--homepage{z-index:2}.homepage-banner-offset-container::after{content:\"\";display:block;position:absolute;bottom:0;z-index:1;left:0;width:100%;height:80%;background:#fff}.section--quicklinks .snowflake-button-full-width a{padding-left:24px!important;padding-right:24px!important;transition:box-shadow .25s cubic-bezier(.4,0,.2,1);text-align:left;display:flex;justify-content:center;align-items:center}.section--quicklinks .snowflake-button-full-width a:hover{box-shadow:0 16px 16px 0 rgb(0 0 0 / .16);transition:box-shadow .25s cubic-bezier(.4,0,.2,1)}.section--quicklinks .snowflake-button-container:focus-visible a::before,.section--quicklinks .snowflake-button-full-width a::before{content:\"\";width:23px;height:23px;flex-shrink:0;margin-right:12px;display:inline-block;background-size:cover;background-repeat:no-repeat;background-position:center}#industryPartnerSlider .snowflake-navigation-icon.swiper-button-disabled,#partnerResources .section--resource-hub a svg,.button-tabs span.snowflake-tabs-navigation-item:after,.customer-card--hide-cta .snowflake-case-study-card-button,.dot-tabs span.snowflake-tabs-navigation-item::after,.partner-sidebar__mobile-expand,html:not(.aem-AuthorLayer-initial):not(.aem-AuthorLayer-Edit) .tab-content:not(.is-active){display:none}.section--quicklinks .snowflake-button-full-width a.pricing::before{background-image:url(https://www.snowflake.com/content/dam/snowflake-site/general/icons/decorative-icons/pricing-icon.svg)}.section--quicklinks .snowflake-button-full-width a.snowflake_on_snowflake::before{background-image:url(https://www.snowflake.com/content/dam/snowflake-site/general/icons/navigation/nav-icon_snowflake-bug.svg)}.section--quicklinks .snowflake-button-full-width a.virtual_hands_on_labs::before{background-image:url(https://www.snowflake.com/content/dam/snowflake-site/general/icons/navigation/nav-icon__training.svg)}.section--quicklinks .snowflake-button-full-width a.weekly_demo::before{background-image:url(https://www.snowflake.com/content/dam/snowflake-site/general/icons/navigation/nav-icon__webinars.svg)}@media screen and (min-width:1024px){.hero--home__inner .snowflake-experience-fragment,.offset-video,.offset-video__bg-image{left:-50%}.section--quicklinks .snowflake-flexible-column-container-items{gap:24px}.snowflake-quote-item-inner{padding:32px 24px 24px!important}}#communitiesOuter_overflowBottomGray::after{max-height:100px}#caseStudyOuter_overflowBottomMidBlue::after{max-height:180px}#caseStudyInner .snowflake-case-study-card .snowflake-wistia-video{border-radius:0!important}#caseStudyInner .snowflake-case-study-card{box-shadow:none!important;border-radius:0}#caseStudyInner{max-width:1200px;margin:0 auto;box-shadow:rgb(152 162 179 / .1) 0 10px 20px 0,rgb(152 162 179 / .25) 0 2px 6px 0;border-radius:8px;overflow:hidden;position:relative;z-index:1}.case-study__logo-bar\u003E.snowflake-flexible-column-container-items{background:#f7f9fa;padding:32px 16px 40px}.case-study__logo-bar .cmp-image__image{width:90%;margin:0 auto;max-width:240px}.hp-platform__text-group\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:not(:first-child),.sc-sidebar__group .snowflake-button-link{margin-top:8px}.workloads_7.unistore{margin-left:auto;margin-right:auto}#homepageFootnotesInner .snowflake-simple-stat-disclaimer .snowflake-text p{color:#fff!important}.snowflake-simple-stat-disclaimer .snowflake-text p\u003Ea{border-bottom:1px solid var(--ui-03);color:var(--text-03)}.snowflake-card-v2-advanced{color:inherit}#workloadCardGridOuter .snowflake-card-v2-base-front{gap:0}.video-modal.snowflake-modal-window-open-inner{background-color:#fff0;padding:8px;border:none}.snowflake-container-arrow-dotted-faded .snowflake-container-arrow-dotted-faded-image{width:40%!important;max-width:420px;top:4%!important}.list--blue-bullets ul{margin:0!important;padding:0!important;list-style-type:none}.list--blue-bullets li{margin:0;padding:0 0 0 32px;position:relative}.list--blue-bullets li::before{content:\"\";display:block;border-radius:100%;background:#29b5e8;width:18px;height:18px;position:absolute;top:4px;left:0;border:5px solid #e5f2f7;box-sizing:border-box}.list--blue-bullets li:not(:last-child){margin-bottom:1rem}.logo-tabs .snowflake-navigation-container,.snowflake-simple-stat-content:empty,.summit-speaker-card .snowflake-card-v2-advanced-text{margin-bottom:0}#techResourceInner,#techResourceOuter,div.overflow-bottom--blue,div.overflow-bottom--gray,div.overflow-bottom--mid-blue,div.overflow-bottom--white,div.overflow-top--blue,div.overflow-top--gray,div.overflow-top--mid-blue,div.overflow-top--white,div[id$=overflowBottomGray],div[id$=overflowBottomMidBlue],div[id$=overflowTopBlue],div[id$=overflowTopGray]{position:relative}div.overflow-bottom--blue::after,div.overflow-bottom--gray::after,div.overflow-bottom--mid-blue::after,div.overflow-bottom--white::after,div.overflow-top--blue::after,div.overflow-top--gray::after,div.overflow-top--mid-blue::after,div.overflow-top--white::after,div[id$=overflowBottomGray]::after,div[id$=overflowBottomMidBlue]::after,div[id$=overflowBottomWhite]::after,div[id$=overflowTopBlue]::after,div[id$=overflowTopGray]::after,div[id$=overflowTopWhite]::after{content:\"\";display:block;position:absolute;left:0;width:100%;height:40%}div.overflow-top--blue::after,div.overflow-top--gray::after,div.overflow-top--mid-blue::after,div.overflow-top--white::after,div[id$=overflowTopBlue]::after,div[id$=overflowTopGray]::after,div[id$=overflowTopWhite]::after{top:0}div.overflow-bottom--blue::after,div.overflow-bottom--gray::after,div.overflow-bottom--mid-blue::after,div.overflow-bottom--white::after,div[id$=overflowBottomGray]::after,div[id$=overflowBottomMidBlue]::after,div[id$=overflowBottomWhite]::after{bottom:0}div.overflow-bottom--white::after,div.overflow-top--white::after,div[id$=overflowBottomWhite]::after,div[id$=overflowTopWhite]::after{background:#fff!important}div.overflow-bottom--gray::after,div.overflow-top--gray::after,div[id$=overflowBottomGray]::after,div[id$=overflowTopGray]::after{background:#f6f9fa!important}div.overflow-bottom--mid-blue::after,div.overflow-top--mid-blue::after,div[id$=overflowBottomMidBlue]::after,div[id$=overflowTopMidBlue]::after{background:#11567f!important}div.overflow-bottom--blue::after,div.overflow-top--blue::after,div[id$=overflowBottomBlue]::after,div[id$=overflowTopBlue]::after{background:#259edc!important}.snowflake-premium-content-banner.promo-banner--no-shadow{box-shadow:none!important}#industryPartnerSlider .cmp-image__image,#industryPartnerSlider .section--partner-tabs .snowflake-image-container .cmp-image__image,#partnerSidebar,.has-shadow .cmp-image__image{box-shadow:0 10px 20px 0 rgb(152 162 179 / .1),0 2px 6px 0 rgb(152 162 179 / .25)}.content-chip--has-desc{align-items:flex-start;padding:20px!important}.content-chip--has-desc .snowflake-content-chip-image{max-width:100px}.content-chip--has-desc .snowflake-content-chip-image__image{aspect-ratio:1}.content-chip--has-desc .snowflake-title-v2-line:first-child{font-size:18px!important}.content-chip--has-desc .snowflake-title-v2-line:nth-child(2){color:#000!important;font-weight:500!important;font-size:16px!important;line-height:22px!important;margin-top:2px!important}.content-chip--has-desc .snowflake-content-chip-button{margin-top:6px!important;font-size:18px!important;display:none}.square-image .snowflake-content-chip-image{aspect-ratio:1;max-width:120px}.section--logo-bar.smaller-logos .snowflake-image-container .cmp-image__image{max-width:200px;margin:0 auto}.snowflake-card-v2-advanced-tag,.snowflake-content-chip-tag{padding:3px 6px!important}.sc-overview__webinar-promo-banner .snowflake-content-chip-button,.snowflake-card-v2-advanced-title:first-child,.summit-pricing-block__aside ul{margin-top:0}.dot-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item{width:40px;height:40px;display:flex;justify-content:center;align-items:center;margin:0!important}.dot-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item p{width:12px;height:12px;background:var(--ui-12);border-radius:100%}.dot-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item p,.logo-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item p{font-size:0!important}.dot-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item.active p{background:var(--ui-01)}.button-tabs .snowflake-navigation-container .swiper-wrapper{padding:8px 0}.button-tabs .snowflake-navigation-container .swiper-slide{margin:0 6px}.button-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item{padding:8px 24px;background-color:#f6f9fa;border-radius:48px;margin:0}.button-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item p{text-transform:uppercase;font-family:Texta,sans-serif;font-weight:700}.button-tabs .border-top{border-top:1px solid #ccc}.button-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item.active{background-color:var(--ui-01);box-shadow:0 2px 6px 0 rgb(152 162 179 / .25),0 10px 20px 0 rgb(152 162 179 / .1)}.button-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item.active p{color:#fff}.button-tabs.has-icons .snowflake-navigation-container .snowflake-tabs-navigation-item p::before{content:\"\";display:inline-block;width:20px;height:20px;background-size:contain;background-repeat:no-repeat;background-position:center center;margin-right:12px;vertical-align:middle;margin-top:-3px}.logo-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item{width:220px;padding-bottom:50%;height:0;margin:0 8px!important;background-size:cover;background-repeat:no-repeat;opacity:.5;transition:opacity .3s}.logo-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item:hover{opacity:.75;transition:opacity .3s}.logo-tabs .snowflake-navigation-container .snowflake-tabs-navigation-item.active{opacity:1;transition:opacity .3s}.dot-tabs .aem-container.cmp-tabs,.logo-tabs .aem-container.cmp-tabs{display:flex;flex-direction:column-reverse}.snowflake-icon.is-center{margin:0 auto;display:block}#industryPartnerSlider .snowflake-flexible-column-container-items,#partnerLogoSquare .snowflake-flexible-column-container-items{gap:24px}#techResourceOuter::after{content:\"\";display:block;position:absolute;top:0;left:0;width:100%;height:40%;background:#f6f9fa}#techResourceInner{z-index:1}.partner-tier-tag h6{display:inline-block!important;padding:2px 6px;border-radius:2px;color:#666}.partner-tier-tag.registered h6{background-color:#f6f9fa}.partner-tier-tag.elite h6{background-color:#11567f;color:#fff}.partner-tier-tag.premier h6{background-color:#b14c77;color:#fff}.partner-tier-tag.select h6{background-color:#5094a0;color:#fff}.partner-details\u003Espan{display:flex;gap:24px}.partner-details a{color:inherit!important;font-weight:400!important}.partner-details p::before{content:\"\";display:inline-block;vertical-align:middle;width:16px;height:16px;background-repeat:no-repeat;background-position:center;transform:translateY(-1px);background-size:auto 90%;margin-right:6px}.partner-details__location::before{background-image:url(\"data:image/svg+xml,%3Csvg width='13' height='18' viewBox='0 0 13 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M6.25 17.7531C6.4375 17.7531 6.6 17.6844 6.7375 17.5531C6.875 17.4219 6.95 17.2531 6.95 17.0531C6.95 16.8531 7.075 16.4281 7.3 15.7969C7.5875 15.0281 7.925 14.3156 8.30625 13.6406C8.8 12.7781 9.3125 12.1031 9.85 11.6094C10.75 10.7969 11.4125 9.96563 11.85 9.12188C12.2875 8.27813 12.5063 7.40313 12.5063 6.49063C12.5063 5.36563 12.2187 4.31563 11.6437 3.33438C11.0937 2.40313 10.3438 1.65938 9.4 1.10938C8.43125 .534376 7.375 .246876 6.24375 .246876C5.1125 .246876 4.06875 .534376 3.0875 1.10938C2.15625 1.65938 1.4125 2.40313 .862498 3.33438C.287498 4.31563 0 5.36563 0 6.49063C0 7.47188 .262499 8.42813 .787499 9.35938C1.14375 10.0031 1.65625 10.6656 2.3125 11.3344C2.75625 11.8031 3.24375 12.4781 3.78125 13.3656C4.225 14.0969 4.63125 14.8594 5 15.6656C5.35 16.3844 5.53125 16.8531 5.55625 17.0656C5.55625 17.2594 5.625 17.4156 5.7625 17.5531C5.9 17.6844 6.0625 17.7531 6.25 17.7531ZM6.16875 14.9156C5.775 14.0656 5.325 13.2469 4.825 12.4594C4.275 11.5594 3.7625 10.8719 3.28125 10.3969C2.625 9.71563 2.1375 9.05938 1.825 8.43438C1.5125 7.80313 1.35625 7.16563 1.35625 6.50313C1.35625 5.61563 1.575 4.80313 2.0125 4.05313C2.45 3.30313 3.04375 2.71563 3.7875 2.27813C4.5375 1.84063 5.35 1.62188 6.2375 1.62188C7.125 1.62188 7.9375 1.84063 8.6875 2.27813C9.4375 2.71563 10.0312 3.30313 10.475 4.04688C10.9187 4.80313 11.1375 5.62188 11.1375 6.50313C11.1375 7.90313 10.3937 9.26563 8.9125 10.5969C8.35 11.1094 7.8125 11.7906 7.3 12.6406C6.88125 13.3344 6.50625 14.0969 6.16875 14.9219V14.9156ZM6.26875 8.36563C6.65625 8.36563 7.01875 8.26563 7.35625 8.07188C7.69375 7.87813 7.95625 7.60938 8.14375 7.28438C8.3375 6.95313 8.43125 6.59063 8.43125 6.19688C8.43125 5.80313 8.33125 5.43438 8.1375 5.10313C7.9375 4.76563 7.675 4.50313 7.3375 4.31563C7 4.12813 6.6375 4.02813 6.24375 4.02813C5.85 4.02813 5.4875 4.12813 5.15625 4.32188C4.825 4.52188 4.56875 4.78438 4.375 5.12188C4.18125 5.45938 4.0875 5.82188 4.0875 6.20938C4.0875 6.59688 4.1875 6.95938 4.38125 7.29688C4.58125 7.63438 4.84375 7.89688 5.18125 8.08438C5.51875 8.27813 5.88125 8.37188 6.26875 8.37188V8.36563ZM6.24375 7.50313C5.8875 7.50313 5.575 7.37188 5.31875 7.11563C5.0625 6.85938 4.93125 6.55313 4.93125 6.19063C4.93125 5.82813 5.0625 5.52188 5.31875 5.26563C5.575 5.00938 5.88125 4.87813 6.24375 4.87813C6.60625 4.87813 6.9125 5.00938 7.16875 5.26563C7.425 5.52188 7.55625 5.82813 7.55625 6.19063C7.55625 6.55313 7.425 6.85938 7.16875 7.11563C6.9125 7.37188 6.60625 7.50313 6.24375 7.50313Z' fill='%2329B5E8'/%3E%3C/svg%3E%0A\")}.partner-details__website::before{background-image:url(\"data:image/svg+xml,%3Csvg width='18' height='16' viewBox='0 0 18 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.61587 2.96889C2.61587 2.75109 2.79633 2.57062 3.01413 2.57062C3.23192 2.57062 3.41238 2.75109 3.41238 2.96889C3.41238 3.18669 3.23192 3.36716 3.01413 3.36716C2.79633 3.36716 2.61587 3.18669 2.61587 2.96889ZM4.21512 2.96889C4.21512 2.75109 4.39558 2.57062 4.61338 2.57062C4.83117 2.57062 5.01163 2.75109 5.01163 2.96889C5.01163 3.18669 4.83117 3.36716 4.61338 3.36716C4.39558 3.36716 4.21512 3.18669 4.21512 2.96889ZM5.81438 2.96889C5.81438 2.75109 5.99484 2.57062 6.21264 2.57062C6.43043 2.57062 6.61089 2.75109 6.61089 2.96889C6.61089 3.18669 6.43043 3.36716 6.21264 3.36716C5.99484 3.36716 5.81438 3.18669 5.81438 2.96889ZM17.2518 .697559H1.19085C.811258 .697559 .506348 1.0025 .506348 1.38209V14.6179C.506348 14.9975 .811258 15.3024 1.19085 15.3024H17.2518C17.6314 15.3024 17.9363 14.9975 17.9363 14.6179V1.38209C17.9363 1.0025 17.6314 .697559 17.2518 .697559ZM16.5673 2.06035V3.90853H1.86914V2.06035H16.5673ZM1.86914 13.9334V4.78593H16.5673V13.9334H1.86914Z' fill='%2329B5E8'/%3E%3C/svg%3E%0A\")}#partnerSidebar{border-radius:4px;background-color:#fff;padding:24px 24px 32px;border-bottom:6px solid #29b5e8}#partnerSidebar h5,.newsletter-disclaimer p{font-size:14px!important}#partnerSidebar ul{margin-top:0;list-style-type:none;padding:0;display:flex;flex-wrap:wrap;gap:8px}#partnerSidebar li{border:1px solid;border-radius:2px;padding:0 4px!important;font-size:11px!important;letter-spacing:.25px;text-transform:uppercase}div.snowflake-partner-hero-card{width:100%;margin:0}.partner-details__logo{max-width:380px;margin:0 auto}@media screen and (max-width:767px){.left-alignment .hp-hero__subheadline{margin-left:auto;margin-right:auto}.left-alignment .hp-hero__headline .snowflake-title-v2-line,.left-alignment .hp-hero__subheadline .snowflake-title-v2-line{text-align:center}.hero--home__inner .snowflake-flexible-column-container-items-top-padding-large{padding-top:var(--spacing-02)}.section--logo-bar\u003E.snowflake-flexible-column-container-items{display:flex;flex-wrap:wrap;flex-direction:row;justify-content:center;gap:8px}.section--logo-bar\u003E.snowflake-flexible-column-container-items\u003Ediv{width:calc(33.33% - 8px)}.partner-sidebar__mobile-expand{display:inline-block;color:#249edc;border-color:#249edc!important}#partnerSidebar li:nth-child(n+6),.summit-nav__links .snowflake-button-tertiary{display:none}.sc-body__sidebar{background-color:#f6f9fa;padding:24px}.sc-body__content{padding:0 24px 24px}.summit-speaker-card .snowflake-card-v2-advanced-content{padding:24px}}#partnerResources h6,.snowflake-tabs-navigation-item p.body-1{font-size:16px!important}#partnerResources .section--resource-hub{padding:0 16px}#partnerResources .section--resource-hub a,.bwalignl{text-align:left}@media screen and (max-width:1023px){.hero--workload .snowflake-hero-system-media-container{width:100%}}.section--timely-content .snowflake-content-chip,.snowflake-mega-nav-dropdown-footer-wrapper{align-items:center}.section--timely-content .snowflake-content-chip-image{max-width:94px}.section--timely-content .snowflake-content-chip-image__inner{line-height:0}.section--timely-content .snowflake-content-chip-image__image{aspect-ratio:1;height:auto}.section--workload-overview .workload-overview__headline{max-width:280px;margin:0 auto}#industryPartnerSlider .swiper-slide{margin-top:0!important;padding:0 12px}#industryPartnerSlider .snowflake-tabs-navigation-item{margin-left:0!important;margin-right:0!important}#industryPartnerSlider .snowflake-premium-content-banner-background-grad-white .snowflake-premium-content-banner{box-shadow:none}#industryPartnerSlider .logo-slider__slide .aem-container{display:flex;padding:0 8px!important;flex-wrap:wrap;gap:16px!important;justify-content:center}#industryPartnerSlider .logo-slider__slide .aem-container\u003Ediv{width:48%;max-width:200px}#useCaseTabs{padding-top:24px;padding-bottom:24px;padding-right:24px}#useCaseTabs .tab-content.is-active{display:block}#useCaseTabs .vert-tab{border-bottom:1px solid #a0bbcc;padding-bottom:16px}#useCaseTabs .vert-tab p{display:inline-block}#useCaseTabs .vert-tab p:hover{cursor:pointer}#useCaseTabs .vert-tab p,#useCaseTabs .vert-tab.is-active p.not-active{color:#249edc}#useCaseTabs .vert-tab p.is-active,#useCaseTabs .vert-tab.is-active p{color:#000}#industryPlatformSection{background-image:url(/adobe/dynamicmedia/deliver/dm-aid--db074ad5-7122-4c51-87a3-76c3aa466182/double-arrow-bg%403x.png);background-repeat:no-repeat}.snowflake-text p.featured-quote__source{font-weight:900!important;text-transform:uppercase;font-size:16px!important;margin-top:2rem!important}.snowflake-text p.featured-quote__title{margin-top:0!important;font-size:16px!important}.snowflake-case-study-card-logo img{width:auto!important;height:100px!important;transform:translateX(-15%)}.snowflake-quote-item-quote-text{font-weight:600!important}#customerStoryStatsInner\u003E.container\u003E.cmp-container\u003E.aem-container{display:flex;flex-direction:row}#customerStoryStat1,#customerStoryStat2{max-width:240px}#storyHighlights{border-radius:4px;padding:1rem}.sc-overview__webinar-promo-banner .snowflake-content-chip-content .snowflake-title-v2-line,.summit-pricing-block__tile .black-blue-text-color .snowflake-title-v2-line{color:#000!important}.snowflake-youtube-embedded-wrapper{border-radius:var(--small-border-radius)}#arcticNavItem::before,#offset::before,#open-source::before{color:var(--text-05);font-family:Texta,sans-serif!important}#offset,.sc-architecture-caption{margin-top:16px}.hero--press .snowflake-title-v2-line{text-transform:none!important}@media screen and (min-width:768px){.subpage-timely-content__inner\u003E.snowflake-flexible-column-container-items{box-shadow:0 10px 20px 0 rgb(152 162 179 / .1),0 2px 6px 0 rgb(152 162 179 / .25);padding:var(--spacing-04);border-radius:4px;overflow:hidden}#partnerLogoSquare{padding:0 0 0 48px}.hero--workload .snowflake-container{max-width:1440px;margin:0 auto!important;align-items:center}#industryPartnerSlider.snowflake-flexible-column-container-2-column-40-60\u003E.snowflake-flexible-column-container-items{grid-template-columns:minmax(40%,4fr) minmax(0,6fr)}#industryPartnerSlider .swiper-slide{padding:0 24px}.sc-body{padding:48px}.sc-body\u003E.snowflake-flexible-column-container-items{grid-template-columns:7fr 3fr;gap:124px}}.snowflake-button-container.has-icon{display:inline-flex;justify-content:center;align-items:center;text-align:left}.snowflake-button-container.has-icon::before{content:\"\";display:inline-block;width:20px;height:20px;margin-right:12px;background-size:contain;background-repeat:no-repeat;background-position:center}.snowflake-button-container.is-video::before{background-image:url(\"data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9 1.28663C13.2523 1.28663 16.7134 4.74768 16.7134 9C16.7134 13.2523 13.2523 16.7134 9 16.7134C4.74768 16.7198 1.28663 13.2588 1.28663 9C1.28663 4.74124 4.74768 1.28663 9 1.28663ZM9 0C4.0336 0 0 4.0336 0 9C0 13.9664 4.0336 18 9 18C13.9728 18 18 13.9664 18 9C18 4.0336 13.9728 0 9 0Z' fill='white'/%3E%3Cpath d='M7.75106 6.18211C7.42941 6.16925 7.16565 6.42658 7.16565 6.74823V11.2772C7.16565 11.7082 7.65457 11.9848 8.02126 11.7597L11.7975 9.4952C12.1578 9.27647 12.1578 8.74252 11.7975 8.52379L8.02126 6.25931C7.93763 6.21428 7.84756 6.18211 7.75106 6.18211Z' fill='white'/%3E%3C/svg%3E%0A\")}.snowflake-button-container.is-github::before{background-image:url(\"data:image/svg+xml,%3Csvg width='20' height='21' viewBox='0 0 20 21' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10 .651794C4.475 .651794 0 5.12679 0 10.6518C0 15.0768 2.8625 18.8143 6.8375 20.1393C7.3375 20.2268 7.525 19.9268 7.525 19.6643C7.525 19.4268 7.5125 18.6393 7.5125 17.8018C5 18.2643 4.35 17.1893 4.15 16.6268C4.0375 16.3393 3.55 15.4518 3.125 15.2143C2.775 15.0268 2.275 14.5643 3.1125 14.5518C3.9 14.5393 4.4625 15.2768 4.65 15.5768C5.55 17.0893 6.9875 16.6643 7.5625 16.4018C7.65 15.7518 7.9125 15.3143 8.2 15.0643C5.975 14.8143 3.65 13.9518 3.65 10.1268C3.65 9.03929 4.0375 8.13929 4.675 7.43929C4.575 7.18929 4.225 6.16429 4.775 4.78929C4.775 4.78929 5.6125 4.52679 7.525 5.81429C8.325 5.58929 9.175 5.47679 10.025 5.47679C10.875 5.47679 11.725 5.58929 12.525 5.81429C14.4375 4.51429 15.275 4.78929 15.275 4.78929C15.825 6.16429 15.475 7.18929 15.375 7.43929C16.0125 8.13929 16.4 9.02679 16.4 10.1268C16.4 13.9643 14.0625 14.8143 11.8375 15.0643C12.2 15.3768 12.5125 15.9768 12.5125 16.9143C12.5125 18.2518 12.5 19.3268 12.5 19.6643C12.5 19.9268 12.6875 20.2393 13.1875 20.1393C17.1375 18.8143 20 15.0643 20 10.6518C20 5.12679 15.525 .651794 10 .651794Z' fill='%23249EDC'/%3E%3C/svg%3E%0A\")}.snowflake-button-container.is-quickstart::before{background-image:url(\"data:image/svg+xml,%3Csvg width='15' height='21' viewBox='0 0 15 21' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.8489 2.79368H11.6439V2.38493C11.6439 1.71368 11.1451 .967427 10.4251 .967427H8.94762C8.80887 .359927 8.37387 .299927 7.89762 .299927H7.23012C6.85512 .299927 6.26637 .299927 6.08637 .967427H4.68387C3.94887 .967427 3.35637 1.74368 3.35637 2.38493V2.79368H1.15137C.738867 2.79368 .401367 3.13118 .401367 3.54368V20.2537C.401367 20.6662 .738867 21.0037 1.15137 21.0037H13.8489C14.2614 21.0037 14.5989 20.6662 14.5989 20.2537V3.54368C14.5989 3.13118 14.2614 2.79368 13.8489 2.79368ZM4.29387 2.38493C4.29387 2.18243 4.54137 1.90493 4.68387 1.90493H6.50262C6.76137 1.90493 6.97137 1.69493 6.97137 1.43618C6.97137 1.33868 6.97887 1.27868 6.98637 1.24118C7.05012 1.23368 7.15512 1.23368 7.23387 1.23368H7.90137C7.95012 1.23368 8.00637 1.23368 8.05137 1.23368C8.05512 1.27868 8.05887 1.34243 8.05887 1.43243C8.05887 1.69118 8.26887 1.90118 8.52762 1.90118H10.4289C10.5301 1.90118 10.7101 2.14493 10.7101 2.38118V2.78993H4.29762V2.38118L4.29387 2.38493ZM13.0989 19.4999H1.90137V4.29368H13.0989V19.5037V19.4999Z' fill='%23249EDC'/%3E%3Cpath d='M3.82512 16.0424H11.1751C11.4339 16.0424 11.6439 15.8324 11.6439 15.5736V6.88486C11.6439 6.62611 11.4339 6.41611 11.1751 6.41611H3.82512C3.56637 6.41611 3.35637 6.62611 3.35637 6.88486V15.5736C3.35637 15.8324 3.56637 16.0424 3.82512 16.0424ZM4.29387 15.1049V13.3686H10.7064V15.1049H4.29387ZM10.7101 7.35361V12.4311H4.29762V7.35361H10.7101Z' fill='%23249EDC'/%3E%3Cpath d='M6.16512 9.35989H8.83887C9.09762 9.35989 9.30762 9.14989 9.30762 8.89114C9.30762 8.63239 9.09762 8.42239 8.83887 8.42239H6.16512C5.90637 8.42239 5.69637 8.63239 5.69637 8.89114C5.69637 9.14989 5.90637 9.35989 6.16512 9.35989Z' fill='%23249EDC'/%3E%3Cpath d='M6.16512 11.3624H8.83887C9.09762 11.3624 9.30762 11.1524 9.30762 10.8937C9.30762 10.6349 9.09762 10.4249 8.83887 10.4249H6.16512C5.90637 10.4249 5.69637 10.6349 5.69637 10.8937C5.69637 11.1524 5.90637 11.3624 6.16512 11.3624Z' fill='%23249EDC'/%3E%3C/svg%3E%0A\")}.snowflake-button-container.is-download::before{background-image:url(\"data:image/svg+xml,%3Csvg width='16' height='18' viewBox='0 0 16 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.2017 17.1637H.798265C.364425 17.1637 0 16.7993 0 16.3655V12.3568C0 11.923 .364425 11.5585 .798265 11.5585C1.2321 11.5585 1.59653 11.923 1.59653 12.3568V15.5498H14.4035V12.3568C14.4035 11.923 14.7679 11.5585 15.2017 11.5585C15.6356 11.5585 16 11.923 16 12.3568V16.3655C16 16.7993 15.6529 17.1637 15.2017 17.1637Z' fill='%23249EDC'/%3E%3Cpath d='M7.94793 12.9642C7.84381 12.9642 7.73969 12.9468 7.63557 12.8947C7.34056 12.7733 7.14967 12.4783 7.14967 12.1485L7.18437 .938127C7.18437 .504287 7.5488 .139862 7.98264 .139862C8.41648 .139862 8.7809 .504287 8.7809 .938127L8.7462 10.257L12.8416 6.33509C13.154 6.02273 13.6746 6.04008 13.9696 6.35244C14.282 6.66481 14.2646 7.18542 13.9523 7.48043L8.50325 12.7386C8.36442 12.8774 8.15617 12.9642 7.94793 12.9642Z' fill='%23249EDC'/%3E%3Cpath d='M7.94793 12.9642C7.73969 12.9642 7.54881 12.8947 7.39262 12.7386L2.03037 7.53249C1.718 7.22012 1.70065 6.71687 2.01301 6.40451C2.32538 6.09214 2.82863 6.07479 3.141 6.38715L8.50325 11.5932C8.81562 11.9056 8.83297 12.4088 8.52061 12.7212C8.36442 12.8774 8.15617 12.9642 7.94793 12.9642Z' fill='%23249EDC'/%3E%3C/svg%3E%0A\")}.snowflake-button-container.is-expand::before{background-image:url(\"data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.64375 10.9125C6.9375 11.2062 6.93125 11.6812 6.64375 11.9687L2.57502 16H3.79375C4.20625 16 4.54376 16.3375 4.54376 16.75C4.54376 17.1625 4.20625 17.5 3.79375 17.5H.756264C.556264 17.5 .36876 17.4187 .22501 17.2812C.22501 17.2812 .206248 17.25 .193748 17.2375C.143748 17.1812 .100004 17.1125 .0625038 17.0437C.0375038 16.9687 .0187492 16.8937 .0187492 16.8187C.0187492 16.8 .0062561 16.7813 .0062561 16.7625V13.725C.0187561 13.3125 .356257 12.9875 .768757 12.9937C1.16876 13 1.48752 13.325 1.50002 13.725V14.9688L5.5875 10.9187C5.88125 10.6312 6.35 10.6312 6.64375 10.9187V10.9125ZM17.5063 .743732C17.5063 .543732 17.425 .356235 17.2875 .218735C17.2875 .218735 17.2562 .199998 17.2437 .193748C17.1875 .137498 17.1188 .0937347 17.0438 .0624847C16.9688 .0374847 16.8938 .0187492 16.8188 .0187492C16.8 .0187492 16.7813 .00623703 16.7625 .00623703H13.725C13.3125 .00623703 12.975 .343745 12.975 .756245C12.975 1.16874 13.3125 1.50623 13.725 1.50623H14.9688L11.1312 5.37498C10.8437 5.67498 10.8563 6.14999 11.1563 6.43124C11.45 6.71249 11.9063 6.70624 12.1938 6.43124L16.0125 2.575V3.79375C16.0125 4.20625 16.35 4.54372 16.7625 4.54372C17.175 4.54372 17.5125 4.20625 17.5125 3.79375V.756245L17.5063 .743732ZM16.7562 12.9688C16.3437 12.9688 16.0063 13.3063 16.0063 13.7188V14.8937L12.1938 10.925C11.9063 10.625 11.4375 10.6188 11.1375 10.9063C10.8375 11.1938 10.8313 11.6625 11.1188 11.9625L15 16.0062H13.7188C13.3063 16.0062 12.9688 16.3437 12.9688 16.7562C12.9688 17.1687 13.3063 17.5063 13.7188 17.5063H16.7562C16.85 17.5063 16.95 17.4875 17.0375 17.45C17.0875 17.425 17.1313 17.3937 17.175 17.3625C17.2063 17.3437 17.2438 17.325 17.275 17.3C17.3313 17.2375 17.375 17.1687 17.4125 17.1C17.4188 17.0875 17.4375 17.075 17.4438 17.0562C17.45 17.025 17.4563 16.9938 17.4625 16.9625C17.4813 16.9 17.5 16.8375 17.5 16.7687V13.725C17.5 13.3125 17.1687 12.975 16.7562 12.975V12.9688ZM.750008 4.53125C1.16251 4.53125 1.50002 4.19374 1.50002 3.78124V2.5L5.59376 6.43124C5.89376 6.71874 6.36251 6.70626 6.65001 6.41251C6.93751 6.11876 6.92501 5.64375 6.63126 5.35625L2.61251 1.49998H3.7875C4.2 1.49998 4.53751 1.16249 4.53751 .749989C4.53751 .337489 4.2 0 3.7875 0H.743752C.668752 0 .600004 .0187355 .531254 .0437355C.506254 .0499855 .481263 .0437477 .462513 .0562477C.443763 .0687477 .425015 .0812462 .406265 .0937462C.337515 .124996 .275004 .168741 .218754 .224991H.212498C.212498 .224991 .175 .28125 .15625 .3125C.11875 .3625 .0812477 .4125 .0562477 .46875C.0374977 .525 .0249992 .587499 .0187492 .643749C.0124992 .674999 0 .712482 0 .743732V3.78124C0 4.19374 .337508 4.53125 .750008 4.53125Z' fill='white'/%3E%3C/svg%3E%0A\")}@keyframes slow-scroll{100%{transform:translateY(-50%)}}.sc-hero{overflow:hidden;background-color:#212d35;background-repeat:repeat-y;background-image:url(\"data:image/svg+xml,%3Csvg width='389' height='17' viewBox='0 0 389 17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M.638672 7.80824L.638672 9.2566C.638672 9.52364 .85538 9.74024 1.12262 9.74024H2.57204C2.83928 9.74024 3.05598 9.52364 3.05598 9.2566V7.80824C3.05598 7.54119 2.83928 7.32472 2.57204 7.32472L1.12262 7.32472C.85538 7.32472 .638672 7.54119 .638672 7.80824Z' fill='url(%23paint0_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.9639 7.80824V9.2566C10.9639 9.52364 11.1806 9.74024 11.4478 9.74024L12.8972 9.74024C13.1645 9.74024 13.3812 9.52364 13.3812 9.2566V7.80824C13.3812 7.54119 13.1645 7.32471 12.8972 7.32471L11.4478 7.32471C11.1806 7.32471 10.9639 7.54119 10.9639 7.80824Z' fill='url(%23paint1_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M21.2891 7.80823V9.2566C21.2891 9.52364 21.5058 9.74024 21.773 9.74024L23.2224 9.74024C23.4897 9.74024 23.7064 9.52364 23.7064 9.2566V7.80823C23.7064 7.54119 23.4897 7.32471 23.2224 7.32471L21.773 7.32471C21.5058 7.32471 21.2891 7.54119 21.2891 7.80823Z' fill='url(%23paint2_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M31.6143 7.80823V9.2566C31.6143 9.52364 31.831 9.74024 32.0982 9.74024H33.5476C33.8149 9.74024 34.0316 9.52364 34.0316 9.2566V7.80823C34.0316 7.54119 33.8149 7.32471 33.5476 7.32471L32.0982 7.32471C31.831 7.32471 31.6143 7.54119 31.6143 7.80823Z' fill='url(%23paint3_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M41.9395 7.80823V9.2566C41.9395 9.52364 42.1562 9.74024 42.4234 9.74024H43.8728C44.1401 9.74024 44.3568 9.52364 44.3568 9.2566V7.80823C44.3568 7.54119 44.1401 7.32471 43.8728 7.32471L42.4234 7.32471C42.1562 7.32471 41.9395 7.54119 41.9395 7.80823Z' fill='url(%23paint4_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M52.5076 7.80823V9.2566C52.5076 9.52364 52.7243 9.74024 52.9916 9.74024H54.441C54.7082 9.74024 54.9249 9.52364 54.9249 9.2566V7.80823C54.9249 7.54119 54.7082 7.32471 54.441 7.32471L52.9916 7.32471C52.7243 7.32471 52.5076 7.54119 52.5076 7.80823Z' fill='url(%23paint5_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M62.8331 7.80823V9.2566C62.8331 9.52364 63.0493 9.74024 63.3165 9.74024H64.7664C65.0332 9.74024 65.2504 9.52364 65.2504 9.2566V7.80823C65.2504 7.54119 65.0332 7.32471 64.7664 7.32471L63.3165 7.32471C63.0493 7.32471 62.8331 7.54119 62.8331 7.80823Z' fill='url(%23paint6_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M73.1583 7.80823V9.2566C73.1583 9.52364 73.3745 9.74024 73.6417 9.74024H75.0916C75.3584 9.74024 75.5756 9.52364 75.5756 9.2566V7.80823C75.5756 7.54119 75.3584 7.32471 75.0916 7.32471L73.6417 7.32471C73.3745 7.32471 73.1583 7.54119 73.1583 7.80823Z' fill='url(%23paint7_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M83.4835 7.80823V9.2566C83.4835 9.52364 83.6997 9.74024 83.9669 9.74024H85.4168C85.6836 9.74024 85.9008 9.52364 85.9008 9.2566V7.80823C85.9008 7.54119 85.6836 7.32471 85.4168 7.32471L83.9669 7.32471C83.6997 7.32471 83.4835 7.54119 83.4835 7.80823Z' fill='url(%23paint8_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M93.8087 7.80823V9.2566C93.8087 9.52364 94.0249 9.74024 94.2921 9.74024H95.742C96.0088 9.74024 96.226 9.52364 96.226 9.2566V7.80823C96.226 7.54119 96.0088 7.32471 95.742 7.32471L94.2921 7.32471C94.0249 7.32471 93.8087 7.54119 93.8087 7.80823Z' fill='url(%23paint9_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M104.134 7.80823V9.2566C104.134 9.52364 104.35 9.74024 104.617 9.74024H106.067C106.334 9.74024 106.551 9.52364 106.551 9.2566V7.80823C106.551 7.54119 106.334 7.32471 106.067 7.32471L104.617 7.32471C104.35 7.32471 104.134 7.54119 104.134 7.80823Z' fill='url(%23paint10_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M114.702 7.80823V9.2566C114.702 9.52364 114.918 9.74024 115.185 9.74024L116.635 9.74024C116.902 9.74024 117.119 9.52364 117.119 9.25659V7.80823C117.119 7.54119 116.902 7.32471 116.635 7.32471L115.185 7.32471C114.918 7.32471 114.702 7.54119 114.702 7.80823Z' fill='url(%23paint11_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M125.027 7.80823V9.25659C125.027 9.52364 125.243 9.74024 125.511 9.74024L126.961 9.74024C127.227 9.74024 127.445 9.52364 127.445 9.25659V7.80823C127.445 7.54119 127.227 7.32471 126.961 7.32471L125.511 7.32471C125.243 7.32471 125.027 7.54119 125.027 7.80823Z' fill='url(%23paint12_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M135.352 7.80823V9.25659C135.352 9.52364 135.569 9.74024 135.836 9.74024H137.286C137.553 9.74024 137.77 9.52364 137.77 9.25659V7.80823C137.77 7.54119 137.553 7.32471 137.286 7.32471L135.836 7.32471C135.569 7.32471 135.352 7.54119 135.352 7.80823Z' fill='url(%23paint13_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M145.678 7.80823V9.25659C145.678 9.52364 145.894 9.74024 146.161 9.74024H147.611C147.878 9.74024 148.095 9.52364 148.095 9.25659V7.80823C148.095 7.54119 147.878 7.32471 147.611 7.32471L146.161 7.32471C145.894 7.32471 145.678 7.54119 145.678 7.80823Z' fill='url(%23paint14_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M156.003 7.80823V9.25659C156.003 9.52364 156.219 9.74024 156.486 9.74024H157.936C158.203 9.74024 158.42 9.52364 158.42 9.25659V7.80823C158.42 7.54119 158.203 7.32471 157.936 7.32471L156.486 7.32471C156.219 7.32471 156.003 7.54119 156.003 7.80823Z' fill='url(%23paint15_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M166.328 7.80823V9.25659C166.328 9.52363 166.544 9.74024 166.811 9.74024H168.261C168.528 9.74024 168.745 9.52363 168.745 9.25659V7.80823C168.745 7.54119 168.528 7.32471 168.261 7.32471L166.811 7.32471C166.544 7.32471 166.328 7.54119 166.328 7.80823Z' fill='url(%23paint16_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M176.896 7.80823V9.25659C176.896 9.52363 177.112 9.74023 177.38 9.74023H178.83C179.096 9.74023 179.313 9.52363 179.313 9.25659V7.80823C179.313 7.54119 179.096 7.32471 178.83 7.32471L177.38 7.32471C177.112 7.32471 176.896 7.54119 176.896 7.80823Z' fill='url(%23paint17_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M187.221 7.80823V9.25659C187.221 9.52363 187.438 9.74023 187.705 9.74023H189.155C189.421 9.74023 189.639 9.52363 189.639 9.25659V7.80823C189.639 7.54119 189.421 7.32471 189.155 7.32471L187.705 7.32471C187.438 7.32471 187.221 7.54119 187.221 7.80823Z' fill='url(%23paint18_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M199.639 7.80824V9.2566C199.639 9.52364 199.855 9.74024 200.123 9.74024H201.572C201.839 9.74024 202.056 9.52364 202.056 9.2566V7.80824C202.056 7.54119 201.839 7.32472 201.572 7.32472L200.123 7.32472C199.855 7.32472 199.639 7.54119 199.639 7.80824Z' fill='url(%23paint19_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M209.964 7.80824V9.2566C209.964 9.52364 210.181 9.74024 210.448 9.74024L211.897 9.74024C212.164 9.74024 212.381 9.52364 212.381 9.2566V7.80824C212.381 7.54119 212.164 7.32471 211.897 7.32471L210.448 7.32471C210.181 7.32471 209.964 7.54119 209.964 7.80824Z' fill='url(%23paint20_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M220.289 7.80823V9.2566C220.289 9.52364 220.506 9.74024 220.773 9.74024L222.222 9.74024C222.49 9.74024 222.706 9.52364 222.706 9.2566V7.80823C222.706 7.54119 222.49 7.32471 222.222 7.32471L220.773 7.32471C220.506 7.32471 220.289 7.54119 220.289 7.80823Z' fill='url(%23paint21_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M230.614 7.80823V9.2566C230.614 9.52364 230.831 9.74024 231.098 9.74024H232.548C232.815 9.74024 233.032 9.52364 233.032 9.2566V7.80823C233.032 7.54119 232.815 7.32471 232.548 7.32471L231.098 7.32471C230.831 7.32471 230.614 7.54119 230.614 7.80823Z' fill='url(%23paint22_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M240.939 7.80823V9.2566C240.939 9.52364 241.156 9.74024 241.423 9.74024H242.873C243.14 9.74024 243.357 9.52364 243.357 9.2566V7.80823C243.357 7.54119 243.14 7.32471 242.873 7.32471L241.423 7.32471C241.156 7.32471 240.939 7.54119 240.939 7.80823Z' fill='url(%23paint23_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M251.508 7.80823V9.2566C251.508 9.52364 251.724 9.74024 251.992 9.74024H253.441C253.708 9.74024 253.925 9.52364 253.925 9.2566V7.80823C253.925 7.54119 253.708 7.32471 253.441 7.32471L251.992 7.32471C251.724 7.32471 251.508 7.54119 251.508 7.80823Z' fill='url(%23paint24_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M261.833 7.80823V9.2566C261.833 9.52364 262.049 9.74024 262.317 9.74024H263.766C264.033 9.74024 264.25 9.52364 264.25 9.2566V7.80823C264.25 7.54119 264.033 7.32471 263.766 7.32471L262.317 7.32471C262.049 7.32471 261.833 7.54119 261.833 7.80823Z' fill='url(%23paint25_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M272.158 7.80823V9.2566C272.158 9.52364 272.374 9.74024 272.642 9.74024H274.092C274.358 9.74024 274.576 9.52364 274.576 9.2566L274.576 7.80823C274.576 7.54119 274.358 7.32471 274.092 7.32471L272.642 7.32471C272.374 7.32471 272.158 7.54119 272.158 7.80823Z' fill='url(%23paint26_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M282.483 7.80823V9.2566C282.483 9.52364 282.7 9.74024 282.967 9.74024H284.417C284.684 9.74024 284.901 9.52364 284.901 9.2566V7.80823C284.901 7.54119 284.684 7.32471 284.417 7.32471L282.967 7.32471C282.7 7.32471 282.483 7.54119 282.483 7.80823Z' fill='url(%23paint27_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M292.809 7.80823L292.809 9.2566C292.809 9.52364 293.025 9.74024 293.292 9.74024H294.742C295.009 9.74024 295.226 9.52364 295.226 9.2566V7.80823C295.226 7.54119 295.009 7.32471 294.742 7.32471L293.292 7.32471C293.025 7.32471 292.809 7.54119 292.809 7.80823Z' fill='url(%23paint28_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M303.134 7.80823L303.134 9.2566C303.134 9.52364 303.35 9.74024 303.617 9.74024H305.067C305.334 9.74024 305.551 9.52364 305.551 9.2566V7.80823C305.551 7.54119 305.334 7.32471 305.067 7.32471L303.617 7.32471C303.35 7.32471 303.134 7.54119 303.134 7.80823Z' fill='url(%23paint29_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M313.702 7.80823L313.702 9.2566C313.702 9.52364 313.918 9.74024 314.185 9.74024L315.635 9.74024C315.902 9.74024 316.119 9.52364 316.119 9.25659V7.80823C316.119 7.54119 315.902 7.32471 315.635 7.32471L314.185 7.32471C313.918 7.32471 313.702 7.54119 313.702 7.80823Z' fill='url(%23paint30_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M324.027 7.80823V9.25659C324.027 9.52364 324.243 9.74024 324.511 9.74024L325.961 9.74024C326.227 9.74024 326.445 9.52364 326.445 9.25659V7.80823C326.445 7.54119 326.227 7.32471 325.961 7.32471L324.511 7.32471C324.243 7.32471 324.027 7.54119 324.027 7.80823Z' fill='url(%23paint31_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M334.352 7.80823V9.25659C334.352 9.52364 334.569 9.74024 334.836 9.74024H336.286C336.553 9.74024 336.77 9.52364 336.77 9.25659L336.77 7.80823C336.77 7.54119 336.553 7.32471 336.286 7.32471L334.836 7.32471C334.569 7.32471 334.352 7.54119 334.352 7.80823Z' fill='url(%23paint32_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M344.678 7.80823V9.25659C344.678 9.52364 344.894 9.74024 345.161 9.74024H346.611C346.878 9.74024 347.095 9.52364 347.095 9.25659L347.095 7.80823C347.095 7.54119 346.878 7.32471 346.611 7.32471L345.161 7.32471C344.894 7.32471 344.678 7.54119 344.678 7.80823Z' fill='url(%23paint33_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M355.003 7.80823V9.25659C355.003 9.52364 355.219 9.74024 355.486 9.74024H356.936C357.203 9.74024 357.42 9.52364 357.42 9.25659L357.42 7.80823C357.42 7.54119 357.203 7.32471 356.936 7.32471L355.486 7.32471C355.219 7.32471 355.003 7.54119 355.003 7.80823Z' fill='url(%23paint34_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M365.328 7.80823V9.25659C365.328 9.52363 365.544 9.74024 365.811 9.74024H367.261C367.528 9.74024 367.745 9.52363 367.745 9.25659V7.80823C367.745 7.54119 367.528 7.32471 367.261 7.32471L365.811 7.32471C365.544 7.32471 365.328 7.54119 365.328 7.80823Z' fill='url(%23paint35_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M375.896 7.80823V9.25659C375.896 9.52363 376.112 9.74023 376.38 9.74023H377.83C378.096 9.74023 378.313 9.52363 378.313 9.25659V7.80823C378.313 7.54119 378.096 7.32471 377.829 7.32471L376.38 7.32471C376.112 7.32471 375.896 7.54119 375.896 7.80823Z' fill='url(%23paint36_linear_8295_70635)'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M386.221 7.80823V9.25659C386.221 9.52363 386.438 9.74023 386.705 9.74023H388.155C388.421 9.74023 388.639 9.52363 388.639 9.25659V7.80823C388.639 7.54119 388.421 7.32471 388.155 7.32471L386.705 7.32471C386.438 7.32471 386.221 7.54119 386.221 7.80823Z' fill='url(%23paint37_linear_8295_70635)'/%3E%3Cdefs%3E%3ClinearGradient id='paint0_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint1_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint2_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint3_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint4_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint5_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint6_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint7_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint8_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint9_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint10_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint11_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint12_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint13_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint14_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint15_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint16_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint17_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint18_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint19_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint20_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint21_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint22_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint23_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint24_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint25_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint26_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint27_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint28_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint29_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint30_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint31_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint32_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint33_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint34_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint35_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint36_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3ClinearGradient id='paint37_linear_8295_70635' x1='-47.5' y1='8.99989' x2='332' y2='8.99989' gradientUnits='userSpaceOnUse'%3E%3Cstop stop-color='%2329B5E8' stop-opacity='.8'/%3E%3Cstop offset='1' stop-color='%2329B5E8' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3C/svg%3E%0A\")}.sc-hero__inner\u003E.snowflake-flexible-column-container-items\u003Ediv:first-child{position:relative;z-index:3}.sc-hero__inner\u003E.snowflake-flexible-column-container-items\u003Ediv:last-child{position:absolute;height:100%;width:100%;top:0;left:-24px}.sc-hero__inner\u003E.snowflake-flexible-column-container-items\u003Ediv:last-child::before{content:\"\";display:block;z-index:1;position:absolute;top:-64px;left:0;width:150%;height:calc(100% + 160px);background-color:rgb(32 44 53 / .9)}.sc-body__content .heading-3-v2,.sc-hero__headline .heading-1-v2{text-transform:none}.sc-body__content span.snowflake-image-caption{display:block!important;font-style:italic}.sc-body__content .snowflake-text p+ul{margin-top:24px!important;padding-left:16px!important}.white-blue-text-color .snowflake-title-v2.solution-center-hero__certification .snowflake-typographyv2\u003Espan.snowflake-title-v2-line{color:#e9eaeb!important;font-size:16px}.white-blue-text-color .snowflake-title-v2.solution-center-hero__certification.is-large .snowflake-typographyv2\u003Espan.snowflake-title-v2-line{color:#fff!important;font-size:18px}.solution-center-hero__certification\u003E.snowflake-title-v2-line\u003Espan:first-child{display:flex;justify-content:flex-start;align-items:center;gap:8px}.solution-center-hero__certification\u003E.snowflake-title-v2-line\u003Espan:first-child::before{content:\"\";display:inline-block;width:16px;height:16px;background-image:url(\"data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8 0C3.58146 0 0 3.58146 0 8C0 12.4185 3.58146 16 8 16C12.4185 16 16 12.4185 16 8C16 3.58146 12.4185 0 8 0ZM12.7184 5.91984L7.33471 11.3026C7.31293 11.3244 7.31293 11.3454 7.29198 11.3454L7.20653 11.4308C6.94933 11.688 6.54132 11.7525 6.21962 11.6235C6.11238 11.5808 6.00514 11.5163 5.9197 11.4308L5.83425 11.3454C5.83425 11.3454 5.83425 11.3236 5.81246 11.3236L3.28149 8.79347C2.93799 8.44997 2.93799 7.87107 3.28149 7.50664L3.36694 7.42119C3.71044 7.07769 4.28934 7.07769 4.65377 7.42119L6.58401 9.35143L11.3877 4.5477C11.7312 4.2042 12.3101 4.2042 12.6746 4.5477L12.76 4.63315C13.0826 4.99758 13.0828 5.55541 12.7184 5.91984Z' fill='%230E8A16'/%3E%3C/svg%3E%0A\");background-size:contain;background-repeat:no-repeat;background-color:#fff;border-radius:100%}.sc-hero__byline{padding-top:8px}.sc-hero__byline p{color:#e2e2e2;margin-top:0!important}.sc-hero pre[class*=language-]{overflow:visible}.snowflake-code-snippet,.snowflake-code-snippet code,.snowflake-code-snippet pre{font-size:16px}.sc-hero__code-snippet:not(pre)\u003Ecode[class*=language-],.sc-hero__code-snippet pre[class*=language-]{background:0 0}.sc-hero__code-snippet{opacity:.8;background-color:transparent!important;position:absolute;top:0;right:0;width:100%;animation:240s linear 1s forwards slow-scroll}.sc-hero__button-container .snowflake-flexible-column-container-items{padding:0 0 24px;margin-top:-8px;margin-left:24px}.sc-sidebar__partner-logo{width:100%;max-width:140px;margin-top:8px}.sc-sidebar__partner-logo .cmp-image__image{border-radius:0}.sc-tag-cluster.snowflake-text ul{list-style-type:none;padding:0;display:flex;flex-wrap:wrap;gap:8px;margin:0}.sc-tag-cluster.snowflake-text li{color:#373f41;border-radius:4px;display:inline-block;padding:6px;text-transform:uppercase;letter-spacing:1px;font-size:12px!important;line-height:12px!important;margin:0!important;background-color:#f3f3f3}.sc-body .share-icon svg{height:24px;cursor:pointer}.sc-body .share-icon svg:hover path{fill:var(--ui-02)}.sc-overview__webinar-promo-banner{align-items:center;border:1px solid #ccc;padding:var(--spacing-02)}.sc-overview__webinar-promo-banner .snowflake-content-chip-image{max-width:32px;margin-right:var(--spacing-02);line-height:0}.sc-overview__webinar-promo-banner .snowflake-content-chip-image__image,.summit-speaker-card .snowflake-card-v2-advanced-image__image{aspect-ratio:1}.sc-overview__webinar-promo-banner .snowflake-content-chip-content .heading-5-v2{font-size:14px;font-family:Lato,sans-serif}.sc-overview__webinar-promo-banner .snowflake-content-chip-content .snowflake-title-v2-line:not(:first-child){font-weight:400}.sc-overview__webinar-promo-banner .snowflake-content-chip-button .snowflake-button-container{font-size:14px!important}.diagram-group__button{position:absolute;bottom:24px;right:24px;background-color:#212c35!important}.section--mountains-bottom,.summit-hp-hero{position:relative}.sc-cert-banner{background-color:#212d35;border-radius:8px;padding:24px;overflow:hidden}.sc-cert-banner\u003E.container\u003E.cmp-container\u003E.aem-container{display:flex;flex-direction:row;align-items:center}:root{--text-secondary:#706f6f;--summit-bg-ltblue:#eaf8fd;--summit-bg-blue:#249edc;--summit-border:#d2d1d4;--summit-border-radius:8px;--summit-card-padding:32px;--summit-card-padding-sm:28px}.section--mountains-bottom::after,.section--mountains-bottom::before{content:\"\";display:block;position:absolute;bottom:-1px;max-width:400px;background-size:100% auto;height:100%;width:30%;line-height:0;background-repeat:no-repeat}.button-group\u003E.container\u003E.cmp-container\u003E.aem-container{justify-content:center;align-items:center}.button-group\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:auto!important;margin:0 8px!important}.button-group .snowflake-button-container{font-family:Texta,sans-serif}.section--summit-bg-ltblue{background-color:var(--summit-bg-ltblue)}.section--summit-bg-blue,.summit-hero-secondary{background-color:var(--summit-bg-blue)}.section--mountains-bottom::before{left:0;background-image:url(\"data:image/svg+xml,%3Csvg width='402' height='309' viewBox='0 0 402 309' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M401.523 308.761H0V0L181.63 182.431L228.479 135.531L401.523 308.761Z' fill='%23249EDC'/%3E%3C/svg%3E%0A\");background-position:bottom left}.section--mountains-bottom::after{right:0;background-image:url(\"data:image/svg+xml,%3Csvg width='402' height='309' viewBox='0 0 402 309' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 308.761H401.523V0L219.893 182.431L173.044 135.531L0 308.761Z' fill='%23249EDC'/%3E%3C/svg%3E%0A\");background-position:bottom right}.summit-hp-hero{overflow:hidden}.summit-hero__bg-video{position:absolute;top:50%;left:50%;width:120%;height:100%;opacity:.3;transform:translate(-50%,-50%)}.summit-hero__bg-svg,.summit-prefooter__bg-image,.summit-secondary-hero__bg-image{position:absolute;bottom:0;left:0;width:100%}.summit-hp-promo-banner__headline .heading-4-v2{font-weight:900}.summit-hero-secondary .hero-lottie__left{position:absolute;bottom:0;left:0;width:30%;line-height:0}.summit-timeline__card::after,.summit-timeline__card::before{bottom:0;left:50%;position:absolute;display:block;background-color:var(--ui-01);content:\"\"}.summit-hero-secondary .snowflake-text p{font-size:24px!important;line-height:32px!important;max-width:720px;margin:0 auto}.summit-stat-container\u003E.container\u003E.cmp-container\u003E.aem-container{display:flex;justify-content:center}.summit-stat-container\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:auto!important;max-width:25%}.summit-stat-container\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:not(:last-child){border-right:1px solid #fff}.summit-timeline__card{border:1px solid var(--summit-border);border-radius:var(--summit-border-radius);padding:var(--summit-card-padding);position:relative;background-color:#fff}.summit-timeline__card::before{width:20px;height:20px;border-radius:100%;transform:translate(-50%,50%)}.summit-timeline__card::after{width:3px;height:50px;transform:translate(-50%,100%)}.summit-timeline-card__icon{width:48px;height:48px}.summit-timeline-card__headline .heading-3-v2{font-size:32px}.faq-group{border:1px solid var(--ui-12);border-radius:4px;background-color:#fff}.faq-group__question{padding:24px}.faq-group__question:hover{color:var(--ui-01);cursor:pointer}.faq-group__question .heading-4-v2,.faq-group__question .heading-5-v2{position:relative;padding-right:64px}.faq-group__question .heading-4-v2::after,.faq-group__question .heading-5-v2::after{content:\"\";display:block;width:32px;height:32px;background-image:url(\"data:image/svg+xml,%3Csvg width='29' height='16' viewBox='0 0 29 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M14.16 14.6807C14.2537 14.7957 14.3719 14.8884 14.506 14.952C14.64 15.0157 14.7866 15.0487 14.935 15.0487C15.0834 15.0487 15.2299 15.0157 15.3639 14.952C15.498 14.8884 15.6162 14.7957 15.71 14.6807V14.6807L28.51 2.00068C29.07 1.43068 29.07 .92068 28.51 .44068C27.95 -.0393204 27.43 -.11932 26.96 .44068L14.94 12.0007L2.99996 .45068C2.90725 .322624 2.7855 .218374 2.6447 .146483C2.50389 .0745926 2.34805 .0371094 2.18996 .0371094C2.03187 .0371094 1.87603 .0745926 1.73522 .146483C1.59442 .218374 1.47267 .322624 1.37996 .45068C.819961 .93068 .819961 1.45068 1.37996 2.01068L14.16 14.6807Z' fill='black'/%3E%3C/svg%3E%0A\");background-size:80% auto;background-repeat:no-repeat;background-position:center;position:absolute;top:-2px;right:0;transition:.3s 150ms}.faq-group__question .heading-5-v2::after{top:-4px}.faq-group__answer{max-height:0;overflow:hidden;width:95%;padding:0 24px;transition:.5s}.faq-group__answer\u003Espan{display:block;padding-bottom:24px}.is-open .faq-group__answer{max-height:600px;transition:1s}.is-open .faq-group__question .heading-4-v2::after,.is-open .faq-group__question .heading-5-v2::after{transform:rotate(180deg);transition:.3s}.summit-agenda{box-shadow:2px 4px 10px 0 rgb(156 156 156 / .52);border-radius:8px;background-color:#fff;max-width:980px;margin-left:auto;margin-right:auto;padding:40px;width:90%}.agenda-item{border-radius:8px;background-color:#d4f0fa;padding:16px;border-left:4px solid var(--ui-01);position:relative}.summit-pricing-block__tile.is-past,.summit-pricing-block__tile.is-upcoming{pointer-events:none;border-color:#d2d1d4}p.agenda-item__time{width:25%;font-family:Texta!important;font-size:32px!important;font-weight:900!important;text-transform:uppercase!important;max-width:140px}@media screen and (max-width:991px){#partnerResources .section--resource-hub .snowflake-button-link .snowflake-button-container{font-size:14px!important;line-height:20px!important;margin-top:4px}#industryPartnerSlider\u003E.snowflake-flexible-column-container-items{display:flex;flex-direction:column}#industryPartnerSlider\u003E.snowflake-flexible-column-container-items\u003Ediv{width:100%}.sc-cert-banner__left{text-align:center}.sc-cert-banner__left .solution-center-hero__certification .snowflake-title-v2-line{justify-content:center}.summit-hero__bg-video{width:200%}.summit-leadership-grid .snowflake-flexible-column-container-items{grid-template-columns:repeat(2,1fr)}.summit-stat-container\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:50%!important;max-width:50%!important}.summit-stat-container\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:not(:last-child){border-right:none!important}.summit-agenda{padding:24px}p.agenda-item__time{font-size:24px!important;width:auto;white-space:nowrap;padding-right:24px}}.agenda-item\u003Espan{display:flex;align-items:center}.summit-add-on-block,.summit-pricing-block{border:1px solid #d2d1d4;border-radius:8px;overflow:hidden;box-shadow:2px 4px 10px 0 rgb(156 156 156 / .52);background-color:#fff}.summit-add-on-block__content,.summit-pricing-block__content{padding:0 20px 20px}.summit-pricing-block__tile{padding:24px 20px;border-radius:4px;background:#fff;border:1px solid var(--ui-01);position:relative;transition:background-color .3s}.summit-pricing-block__tile:hover{background-color:var(--ui-01);transition:background-color .3s}.summit-pricing-block__tile.is-past{background-color:#d4f0fa}.summit-pricing-block__tile:hover .black-blue-text-color .snowflake-title-v2-line{color:#fff!important;transition:color .3s}.partner-card__logo-grid\u003E.container\u003E.cmp-container\u003E.aem-container::after,.partner-card__logo-grid\u003E.container\u003E.cmp-container\u003E.aem-container::before,.summit-add-on-block__content\u003E.container\u003E.cmp-container\u003E.aem-container::after,.summit-add-on-block__content\u003E.container\u003E.cmp-container\u003E.aem-container::before,.summit-pricing-block__tile.is-past .snowflake-content-chip-button,.summit-pricing-block__tile.is-upcoming .snowflake-content-chip-button,.summit-speaker-card .snowflake-card-v2-advanced-tag-indicator{display:none}.summit-pricing-block__tile.is-past .black-blue-text-color .snowflake-title-v2-line{color:#7cc7eb!important}.summit-pricing-block__tile.is-upcoming .black-blue-text-color .snowflake-title-v2-line{color:#8c8c8c!important}.summit-pricing-block__aside{background-color:#d4f0fa;border:1px solid #d2d1d4;border-radius:8px;padding:24px;width:100%}.summit-pricing-block__aside li::marker{color:var(--ui-01)}.summit-pricing-block__aside-headline .heading-5-v2{font-weight:900;margin-bottom:12px}.summit-pricing-block__header{background:#000;padding:24px 40px}.summit-pricing-block__header .heading-4-v2{font-weight:900;letter-spacing:.5px}.bwwidth100,.snowflake-mega-nav-dropdown-footer-content,.summit-pricing-block__tile .black-blue-text-color{width:100%}.summit-pricing-block__tile .heading-5-v2{position:static}.summit-pricing-block__tile .heading-5-v2 span.snowflake-title-v2-line:first-child{text-transform:uppercase;font-weight:900!important;letter-spacing:.25px;font-size:24px!important}.summit-pricing-block__tile .heading-5-v2 span.snowflake-title-v2-line:nth-child(2){margin-top:8px;font-family:Lato,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:16px}.summit-pricing-block__tile .heading-5-v2 span.snowflake-title-v2-line:last-child{font-weight:900!important;font-size:40px!important}.snowflake-mega-nav-nav-item\u003Ea:hover .snowflake-mega-nav-nav-item-title-wrapper\u003E.snowflake-mega-nav-nav-item-title,.summit-pricing-block__tile:not(.is-upcoming):not(.is-past) .heading-5-v2 span.snowflake-title-v2-line:last-child{color:var(--ui-01)!important}.summit-pricing-block__tile:hover:not(.is-upcoming):not(.is-past) .heading-5-v2 span.snowflake-title-v2-line:last-child{color:#fff!important}.summit-pricing-block__tile.is-past .heading-5-v2 span.snowflake-title-v2-line:last-child{text-decoration:line-through}.summit-pricing-block__tile .snowflake-content-chip-button{margin-top:0;white-space:nowrap;display:none}.snowflake-card-v2-advanced.no-link{pointer-events:none!important}.snowpro-card{border:1px solid var(--summit-border);border-radius:var(--summit-border-radius);padding:var(--summit-card-padding-sm);display:flex;height:100%}.snowpro-card__headline{margin:24px 0 12px}.snowpro-card__pricing{margin-top:48px}.snowpro-card .snowflake-text .snowpro-card__price{color:var(--ui-01);font-weight:900;font-size:40px!important;font-family:Texta,sans-serif}.summit-stat-container\u003E.container\u003E.cmp-container\u003E.aem-container{display:flex;flex-direction:row;flex-wrap:wrap}.summit-stat-container\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:not(:last-child){border-right:1px solid var(--summit-border)}.summit-stat-card{padding:0 40px}.summit-stat .heading-2-v2 .snowflake-title-v2-line:first-child{font-size:64px;line-height:52px;margin-bottom:8px}.summit-stat .heading-2-v2 .snowflake-title-v2-line:last-child{font-size:32px;line-height:30px;margin-bottom:16px}.summit-speaker-card .snowflake-card-v2-advanced-title{margin-bottom:var(--spacing-01)}.summit-add-on-card{padding:24px;border:1px solid #d2d1d4;border-radius:8px}.summit-add-on__subhead{padding-left:40px;padding-right:40px}.partner-card__logo-grid,.partner-card__logo-single{padding:40px}.partner-card__logo-grid .snowflake-image-container .cmp-image__image,.partner-card__logo-single .snowflake-image-container .cmp-image__image{border-radius:0;max-width:240px;margin:0 auto}.partner-card\u003E.container,.partner-card\u003E.container\u003E.aem-container,.partner-card\u003E.container\u003E.cmp-container{height:100%}.summit-add-on-block__content\u003E.container\u003E.cmp-container\u003E.aem-container{display:flex;flex-direction:row;gap:24px;align-items:stretch}.partner-card__logo-grid\u003E.container\u003E.cmp-container\u003E.aem-container{display:flex;flex-direction:row;flex-wrap:wrap;gap:40px 24px;justify-content:center;align-items:center}.partner-card__logo-grid\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:calc(33.3333% - 24px);margin:0!important}.partner-card{border-radius:8px;border:1px solid #d2d1d4;overflow:hidden;height:100%;background-color:#fff}.partner-card__header{padding:16px 24px;border-bottom:1px solid #d2d1d4}.partner-card__header.is-purple{background-color:#7d44cf}.partner-card__header h4{display:flex;flex-direction:row!important;align-items:center;gap:12px}.partner-card__header h4::before{vertical-align:middle;content:\"\";display:inline-block;width:20px;height:20px;background-size:contain;background-repeat:no-repeat;background-image:url(\"data:image/svg+xml,%3Csvg width='21' height='23' viewBox='0 0 21 23' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M20.0375 12.8374C20.1644 12.439 20.2172 12.0289 20.2077 11.6237C20.193 11.3305 20.1548 11.0373 20.0712 10.7441C19.8196 9.83306 19.223 9.01989 18.3294 8.50724L5.61817 1.2017C3.82388 .173815 1.53618 .784335 .506483 2.56804C-.533615 4.34915 .0797871 6.62351 1.87408 7.65398L8.97715 11.7427L1.87408 15.8201C.0797871 16.8527 -.531016 19.1271 .506483 20.9156C1.53618 22.6941 3.82388 23.302 5.61817 22.2746L18.3294 14.9643C19.1871 14.4728 19.7693 13.7027 20.0375 12.8374Z' fill='black'/%3E%3C/svg%3E%0A\")}.partner-card__header.is-purple h4::before{background-image:url(\"data:image/svg+xml,%3Csvg width='21' height='23' viewBox='0 0 21 23' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M20.0375 12.8374C20.1644 12.439 20.2172 12.0289 20.2077 11.6237C20.193 11.3305 20.1548 11.0373 20.0712 10.7441C19.8196 9.83306 19.223 9.01989 18.3294 8.50724L5.61817 1.2017C3.82388 .173815 1.53618 .784335 .506483 2.56804C-.533615 4.34915 .0797871 6.62351 1.87408 7.65398L8.97715 11.7427L1.87408 15.8201C.0797871 16.8527 -.531016 19.1271 .506483 20.9156C1.53618 22.6941 3.82388 23.302 5.61817 22.2746L18.3294 14.9643C19.1871 14.4728 19.7693 13.7027 20.0375 12.8374Z' fill='white'/%3E%3C/svg%3E%0A\")}.sf-blue-mountains{background-size:90% auto;background-repeat:no-repeat;background-position:center bottom;background-image:url(\"data:image/svg+xml,%3Csvg width='1361' height='410' viewBox='0 0 1361 410' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1360.25 410L1065.53 114.309L976.256 203.875L773.049 0L364.393 410H1360.25Z' fill='%233AA8DF'/%3E%3Cpath d='M274.778 410L137.467 272.238L.15625 410H274.778Z' fill='%233AA8DF'/%3E%3C/svg%3E%0A\")}.bwalignr,.main-pr-body .bwalignr{text-align:right}.bwblockalignl{margin-left:0;margin-right:auto}.bwcellpmargin{margin-top:0;margin-bottom:0}.bwlistdisc{list-style-type:disc}.bwpadb3{padding-bottom:4px}.bwpadb4{padding-bottom:5px}.bwpadl0{padding-left:0}.bwpadl3{padding-left:15px}.bwpadl6{padding-left:30px}.bwpadl9{padding-left:45px}.bwpadl12{padding-left:60px}.bwpadr0{padding-right:0}.bwtablemarginb{margin-bottom:10px}.bwvertalignb{vertical-align:bottom}.bwvertalignt{vertical-align:top}.bwsinglebottom{border-bottom:1pt solid #000}.bwdoublebottom{border-bottom:2.25pt double #000}.bwwidth1{width:1%}.bwwidth2{width:2%}.bwwidth6{width:6%}.bwwidth7{width:7%}.bwwidth8{width:8%}.bwwidth10{width:10%}.bwwidth12{width:12%}.bwwidth32{width:32%}.bwwidth44{width:44%}.bwwidth72{width:72%}.bwwidth97{width:97%}.main-pr-body{font-size:18px;line-height:26px}.main-pr-body img{display:block;width:100%;height:auto!important;border-radius:var(--small-border-radius)}.main-pr-body table{width:100%;display:block}.main-pr-body tbody{background-color:#f7f7f7}.main-pr-body .bwsinglebottom{border-bottom:1pt solid #000!important}.main-pr-body td.bwwidth44{padding-right:40px}.main-pr-body .bw-release-story{font-family:Lato,sans-serif}.main-pr-body .bw-release-story sup,.snowflake-mega-nav-dropdown-header-content-right a{white-space:nowrap}.main-pr-body .bw-release-story\u003E*,.main-pr-body\u003Espan\u003E*{margin-bottom:2rem!important}.snowflake-text.main-pr-body tbody,.snowflake-text.main-pr-body tbody p{font-size:14px!important;line-height:20px!important;width:100%;display:block}.press-body .snowflake-flexible-column-container-items{gap:var(--spacing-08)}.about-snowflake{border:1px solid #ccc;background-color:var(--ui-background-05);padding:24px;border-radius:8px;margin-top:0}.about-snowflake__logo{max-width:140px;margin-top:16px}.hero--press .snowflake-hero-system-inner{max-width:1408px;margin:0 auto!important}#arcticNavItem{flex-direction:column}#arcticNavItem::before{content:\"Featured Open Source Technologies\";display:block;margin-top:48px;margin-bottom:24px;font-size:16px!important;line-height:16px!important;font-weight:800!important;text-transform:uppercase}@media screen and (min-width:768px){.sc-hero__inner\u003E.snowflake-flexible-column-container-items\u003Ediv:last-child{position:relative;height:100%;top:auto;left:auto;width:auto}.sc-hero__inner\u003E.snowflake-flexible-column-container-items\u003Ediv:last-child::before{background:linear-gradient(180deg,#202c35 -7.5%,#fff0 51.25%,#202c35 107.69%)}.sc-hero__byline\u003Espan{display:flex;flex-wrap:wrap}.sc-hero__byline p:not(:last-child)::after{content:\"|\";margin:0 12px;opacity:.5}.sc-hero__button-container .snowflake-flexible-column-container-items{position:absolute;bottom:0;padding:0;margin:0 24px 0 0}.sc-hero__button-container .hero-watch-the-demo{padding:12px 16px!important;float:right;margin-bottom:48px;background-color:rgb(35 45 54 / .8)}.summit-overview-stat{padding:0 40px}.summit-timeline{border-bottom:3px solid var(--ui-01);margin-bottom:64px}.summit-add-on-block__content,.summit-pricing-block__content{padding:0 40px 40px}#arcticNavItem::before{font-size:12px!important;margin-bottom:8px;margin-top:16px}.snowflake-mega-nav-nav-item-title-wrapper\u003E.snowflake-mega-nav-nav-item-title{line-height:20px!important}.snowflake-card .heading-2.snowflake-title-line{font-size:24px!important;line-height:28px!important}}@media screen and (min-width:992px){.hp-hero__eyebrow a{gap:12px;margin-left:0;margin-right:0}.hp-hero__eyebrow a::after{content:\"\";background-image:url(\"data:image/svg+xml,%3Csvg width='6' height='11' viewBox='0 0 6 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M5.49134 5.79438C5.53447 5.75922 5.56923 5.71489 5.5931 5.66463C5.61697 5.61436 5.62935 5.55941 5.62935 5.50376C5.62935 5.44811 5.61697 5.39316 5.5931 5.34289C5.56923 5.29263 5.53447 5.2483 5.49134 5.21314L.736339 .413136C.522589 .203135 .331339 .203135 .151339 .413136C-.0286612 .623135 -.0586612 .818135 .151339 .994386L4.48634 5.50188L.155089 9.97938C.107068 10.0142 .0679743 10.0598 .0410153 10.1126C.0140562 10.1654 0 10.2238 0 10.2831C0 10.3424 .0140562 10.4009 .0410153 10.4537C.0679743 10.5065 .107068 10.5521 .155089 10.5869C.335089 10.7969 .530089 10.7969 .740089 10.5869L5.49134 5.79438Z' fill='black'/%3E%3C/svg%3E%0A\");display:inline-block;width:12px;height:12px;background-repeat:no-repeat;background-size:auto 100%;background-position:left center}.promo-banner--homepage{padding-top:32px}.homepage-banner-offset-container::after{height:50%}#storyHighlights{padding:2rem}.body-display-v2.snowflake-quote-item-quote-text{line-height:28px!important}.snowflake-hero-system-headline .heading-1-v2{line-height:48px;font-size:54px!important}.sc-overview__webinar-promo-banner .snowflake-content-chip-content{flex-direction:row;justify-content:space-between;align-items:center;width:100%}.sc-overview__webinar-promo-banner .snowflake-content-chip-content .heading-5-v2{flex-direction:row}.sc-overview__webinar-promo-banner .snowflake-content-chip-content .snowflake-title-v2-line:not(:first-child)::before{content:\"|\";margin:0 6px}.sc-cert-banner{padding:40px}.sc-cert-banner\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{margin:0!important;width:50%}.sc-cert-banner\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:first-child{flex-grow:1;padding-right:24px}.sc-cert-banner\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:last-child{max-width:240px}.summit-pricing-block__content\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:last-child{width:70%;padding-left:40px}.summit-pricing-block__content\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:first-child{width:30%}.summit-add-on-block__content\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:calc(33.3333% - 24px);margin:0!important;display:flex}.summit-pricing-block__tile .snowflake-content-chip-content{display:flex;flex-direction:row;align-items:center;width:calc(100% - 200px)}.summit-pricing-block__tile .heading-5-v2 span.snowflake-title-v2-line:last-child{position:absolute;top:50%;transform:translate(0,-50%);right:40px}.press-body\u003E.snowflake-flexible-column-container-items\u003Ediv:last-child{position:sticky;top:120px}.snowflake-mega-nav-navigation-title:hover{color:var(--ui-01)}}@media screen and (min-width:1024px){.about-snowflake{padding:28px}.about-snowflake__logo{max-width:none;padding:0 0 0 48px;margin-bottom:0}.hero--press .snowflake-hero-system-layout-70-30 .snowflake-hero-system-content-container{width:85%}.snowflake-hero-system{padding-bottom:var(--spacing-04);padding-top:var(--spacing-07)}.hero--press .display-2-v2{font-size:64px;line-height:56px}.about-snowflake\u003E.container\u003E.cmp-container\u003E.aem-container{flex-direction:row;flex-wrap:nowrap;align-items:center}.about-snowflake\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:last-child{max-width:280px}.about-snowflake\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:first-child{flex-grow:1;margin-bottom:0!important}#polarisNavItem{margin-top:40px}.snowflake-mega-nav-nav-item-description{line-height:18px!important}.snowflake-mega-nav-column-items{gap:var(--spacing-01);grid-gap:var(--spacing-01)}.snowflake-mega-nav-navigation-title{text-transform:none}}div[id*=blueIcon] .snowflake-mega-nav-nav-item-icon__inner{background:var(--ui-01);padding:8px}div[id*=blueIcon]:hover .snowflake-mega-nav-nav-item-icon__inner{background:var(--ui-01)!important}.snowflake-mega-nav-nav-item-icon__inner{border-radius:4px;background:var(--ui-background-05);padding:6px}.snowflake-mega-nav-nav-item:hover .snowflake-mega-nav-nav-item-icon__inner{background:#fff!important}.snowflake-mega-nav-nav-item-icon.snowflake-image-container{height:40px;width:40px}.snowflake-mega-nav-dropdown-footer-links\u003E.snowflake-button-link\u003E.snowflake-button-container{font-size:16px!important;font-family:Texta!important;font-weight:800!important}.snowflake-mega-nav-dropdown-footer-icon.snowflake-image-container{margin-right:8px;width:40px!important;height:40px!important}#viewAllCapabilities a:hover{background:0 0!important}#platformFooter .snowflake-title-v2 .snowflake-title-v2-line:last-child{font-family:Lato;font-size:14px;font-weight:500}#platformFooter .snowflake-mega-nav-dropdown-footer-links{flex-grow:1;justify-content:flex-end;align-items:center}#platformFooter .snowflake-mega-nav-dropdown-footer-content{flex-direction:row}#offset,#open-source{flex-direction:column;border-top:1px solid #ccc}#offset::before,#open-source::before{content:\" \";display:block;width:100%;font-weight:800!important;font-size:12px!important;line-height:14px;text-transform:uppercase;white-space:nowrap;margin-top:16px;margin-bottom:8px}#open-source::before{content:\"Open Source Technologies\"}.snowflake-mega-nav-dropdown-menu-close-button{margin:var(--spacing-04) 0 var(--spacing-03)}.snowflake-mega-nav-column{gap:var(--spacing-02)!important}.snowflake-mega-nav-nav-item\u003Ea{width:100%;margin-left:-8px;padding:8px;border-radius:4px}.snowflake-mega-nav-nav-item\u003Ea:hover{background-color:var(--ui-background-05)}.snowflake-mega-nav-nav-item-description{margin-top:2px;display:block}#promobanner_overflowBottomDarkBlue::before{content:'';display:block;position:absolute;bottom:0;left:0;width:100%;height:50%;background:#212d35}#promobanner_overflowTopDarkBlue::before{content:'';display:block;position:absolute;top:0;left:0;width:100%;height:50%;background:#212d35}.overview-card\u003Ediv{box-shadow:0 0 14px 0 rgba(0,0,0,.10);background-color:#fff;border-radius:16px;overflow:hidden}.overview-card-text{padding:40px}.overview-card-image img{border-radius:0 !important}.overview-card-text h3,.overview-card-text .heading-3-v2{font-size:18px;line-height:1.1;margin-top:0}","isGSAPEnabled":false,":type":"snowflake-site/components/markup-editor"},"mega_header":{"additionalClasses":"heap-nav-header","layout":"SIMPLE","id":"container-4126620d1e",":type":"snowflake-site/components/mega-header",":items":{"nav_mega":{"activeItem":"item_1719963657751_c_663444255","id":"tabs-2de456f6ef",":type":"snowflake-site/components/nav/nav-mega",":items":{"item_1719963657751_c_663444255":{"id":"nav-dropdown-menu-e31bd2bfb4","enableDropdown":true,"nav_column_container":{"layout":"SIMPLE","id":"container-3c11905e82",":type":"snowflake-site/components/nav/nav-column/nav-column-container",":items":{"nav_column":{"additionalClasses":"nav-platform-sidebar","numberOfSubColumns":"one-column","minWidth":"230","maxWidth":"350","layout":"SIMPLE","id":"container-0948d548f2",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item_copy_copy_2_793631646":{"id":"nav-item-a61245ed77","additionalClasses":"nav-item__platform-parent is-platform","linkDescription":"Develop AI products, apps and more on a fully managed platform that securely connects businesses globally — across any type or scale of data.","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/platform/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"The Snowflake Platform"},":type":"snowflake-site/components/nav/nav-item"},"nav_item":{"id":"nav-item-0029b52cce","additionalClasses":"nav-item nav-item--si is-si","linkDescription":"All your knowledge. One trusted enterprise agent.","flag":"NOW GA","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/snowflake-cowork/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Snowflake CoWork"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_copy_2_836345186":{"id":"nav-item-c04ae92c24","additionalClasses":"blue-icon is-analytics","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/analytics/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Analytics"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_copy_2":{"id":"nav-item-6063828c81","additionalClasses":"blue-icon is-ai","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/ai/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"AI"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_copy_2_1314771042":{"id":"nav-item-837c5ddc4b","additionalClasses":"blue-icon is-data-eng","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/data-engineering/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Data Engineering"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_144634":{"id":"nav-item-bd9d242b19","additionalClasses":"blue-icon is-apps-collab","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/applications-and-collaboration/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Applications & Collaboration"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_144634_2013333117":{"id":"nav-item-ac59d50716","additionalClasses":"blue-icon is-transactions","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/transactions/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Transactions"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item_copy_copy_2_793631646","nav_item","nav_item_copy_copy_2_836345186","nav_item_copy_copy_2","nav_item_copy_copy_2_1314771042","nav_item_copy_144634","nav_item_copy_144634_2013333117"]},"nav_column_copy_copy":{"additionalClasses":"meganav-platform-features","navColumnTitle":"Featured Capabilities","numberOfSubColumns":"one-column","layout":"SIMPLE","id":"container-d3d0d96a87",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item_copy_212715":{"id":"nav-item-6acc01698b","additionalClasses":"is-cortex-code","linkDescription":"Snowflake-native AI coding agent ","flag":"New","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/snowflake-coco/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Snowflake CoCo"},":type":"snowflake-site/components/nav/nav-item"},"nav_item":{"id":"nav-item-b575e9a45c","additionalClasses":"is-cortex-ai","linkDescription":"Instant access to industry-leading LLMs","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/product/features/cortex/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Cortex AI"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_660590635":{"id":"nav-item-bb18929d33","additionalClasses":"is-marketplace","linkDescription":"Third-party data sources connected within minutes","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/product/features/marketplace/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Marketplace"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_660590":{"id":"nav-item-bc670c6658","additionalClasses":"is-snowpark","linkDescription":"Libraries and code execution environments that run Python and more","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/product/features/snowpark/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Snowpark"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_660590_983061516":{"id":"nav-item-8053cac9d2","additionalClasses":"is-streamlit","linkDescription":"Framework for transforming Python scripts into web apps","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/product/features/streamlit-in-snowflake/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Streamlit"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item_copy_212715","nav_item","nav_item_copy_660590635","nav_item_copy_660590","nav_item_copy_660590_983061516"]},"nav_column_692142673":{"navColumnTitle":" ","numberOfSubColumns":"one-column","layout":"SIMPLE","id":"container-434bf92e7c",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item_copy_660590_1739526127":{"id":"nav-item-d01056a53e","additionalClasses":"is-postgres","linkDescription":"Fully compatible open source Postgres running on Snowflake","flag":"Now GA","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/features/postgres/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Postgres"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_185565":{"id":"nav-item-f3f70d2ae6","additionalClasses":"is-dcr","linkDescription":"Streamlined model development and MLOps from a centralized UI","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/features/end-to-end-ml-workflows/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Snowflake ML"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_212715":{"id":"nav-item-afadcf475d","additionalClasses":"is-openflow","linkDescription":"Effortless data movement for integrations","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/product/features/openflow/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Openflow"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_660590":{"id":"nav-item-1c51867805","additionalClasses":"is-notebooks","linkDescription":"Interactive dev environment for data and AI teams","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/product/features/notebooks/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Notebooks"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_258535199":{"id":"nav-item-adc36333c5","propertiesId":"workload-nav-1","additionalClasses":"is-native-apps","linkDescription":"End-to-end, Snowflake-native app creation and distribution","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/product/features/native-apps/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Native Apps"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item_copy_660590_1739526127","nav_item_copy_185565","nav_item_copy_212715","nav_item_copy_660590","nav_item_258535199"]},"nav_column_782221091":{"navColumnTitle":" ","numberOfSubColumns":"one-column","layout":"SIMPLE","id":"container-89f92561f3",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item_copy":{"id":"nav-item-b90b5c5c09","additionalClasses":"is-light-gray-icon is-horizon-catalog","linkDescription":"Universal AI catalog","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/features/horizon/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Horizon Catalog"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_660590_1293798742":{"id":"nav-item-80f5b94858","additionalClasses":"is-snowflake-ml","linkDescription":"Governed context layer that keeps AI, BI and data apps working from one truth","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/features/horizon-context/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Horizon Context"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_511717659_c":{"id":"nav-item-1707920c71","additionalClasses":"is-unistore","linkDescription":"Unify transactional and analytical workloads in Snowflake for enhanced simplicity","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/features/unistore/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Unistore"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_511717659_c_1443811525":{"id":"nav-item-bd44ee7e66","additionalClasses":"is-observe","linkDescription":"AI-powered observability for faster production troubleshooting","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/observe/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Observe"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_511717659_c_1006104884":{"id":"nav-item-a1dbd28de6","additionalClasses":"is-observe","linkDescription":"Use any engine on a single governed data copy","flag":"Now GA","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/product/use-cases/interoperable-lakehouse/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Interoperable Lakehouse"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item_copy","nav_item_copy_660590_1293798742","nav_item_511717659_c","nav_item_511717659_c_1443811525","nav_item_511717659_c_1006104884"]}},":itemsOrder":["nav_column","nav_column_copy_copy","nav_column_692142673","nav_column_782221091"]},":type":"snowflake-site/components/nav/nav-dropdown-menu","cq:panelTitle":"Product"},"nav_dropdown_menu_2":{"id":"nav-dropdown-menu-0d3cf5a97f","enableDropdown":true,"nav_column_container":{"layout":"SIMPLE","id":"container-b75bb76f77",":type":"snowflake-site/components/nav/nav-column/nav-column-container",":items":{"nav_column":{"navColumnTitle":"INDUSTRIES","numberOfSubColumns":"one-column","minWidth":"280","layout":"SIMPLE","id":"container-510c39313f",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item_copy_361384_2056203141":{"id":"nav-item-9a63658403","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/industries/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"All Industries"},":type":"snowflake-site/components/nav/nav-item"},"nav_item":{"id":"nav-item-f28f33837c","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/industries/advertising-media-entertainment/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Advertising, Media & Entertainment"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy":{"id":"nav-item-4650a38c5e","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/industries/financial-services/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Financial Services"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_1970515619":{"id":"nav-item-61e5c9e70b","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/industries/healthcare-and-life-sciences/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Healthcare & Life Sciences"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_1533429516":{"id":"nav-item-a2eb342820","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/industries/manufacturing/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Manufacturing"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_1444458226":{"id":"nav-item-1505988650","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/industries/public-sector/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Public Sector"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_1149488919":{"id":"nav-item-36ff1e35c6","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/industries/retail-consumer-goods/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Retail & Consumer Goods"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_57417040":{"id":"nav-item-cf4b856122","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/industries/technology/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Technology"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_361384674":{"id":"nav-item-5a99290701","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/industries/telecom/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Telecom"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_361384":{"id":"nav-item-d34e3b127c","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/industries/travel-hospitality/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Travel & Hospitality"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item_copy_361384_2056203141","nav_item","nav_item_copy","nav_item_copy_1970515619","nav_item_copy_1533429516","nav_item_copy_1444458226","nav_item_copy_1149488919","nav_item_copy_57417040","nav_item_copy_361384674","nav_item_copy_361384"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-extra-small"},"nav_column_copy":{"navColumnTitle":"DEPARTMENTS","numberOfSubColumns":"one-column","minWidth":"160","layout":"SIMPLE","id":"container-52323abdf8",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item":{"id":"nav-item-1bc0784b82","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/solutions/departments/finance/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Finance"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy":{"id":"nav-item-659987480d","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/solutions/departments/information-technology/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"IT"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_1970515619":{"id":"nav-item-99d24dd532","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/solutions/departments/marketing/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Marketing"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item","nav_item_copy","nav_item_copy_1970515619"]},"nav_column_833417450":{"navColumnTitle":"Enablement Solutions","numberOfSubColumns":"one-column","layout":"SIMPLE","id":"container-2e5fd02bf3",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item_copy_107772":{"id":"nav-item-e9c3b6ef23","linkDescription":"Confident migration to a unified platform","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/migrate-to-the-cloud/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Migrate to the AI Data Cloud"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/nav_dropdown_menu_2/nav_column_container/nav_column_833417450/nav_item_copy_107772/icon.coreimg.svg/1723828484100/nav-icon-cloud.svg","alt":"Cloud icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_copy":{"id":"nav-item-80519b5b25","linkDescription":"Snowflake experts to help you accelerate and achieve business goals","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/solutions/services-delivery/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Services Delivery"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/nav_dropdown_menu_2/nav_column_container/nav_column_833417450/nav_item_copy_copy/icon.coreimg.svg/1768354429188/nav-icon--migrate.svg","alt":"Migrate icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item_copy_107772","nav_item_copy_copy"]},"nav_column_copy_copy":{"navColumnTitle":"PARTNER SOLUTIONS","numberOfSubColumns":"one-column","layout":"SIMPLE","id":"container-03f3779aa7",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item":{"id":"nav-item-154dd93f9f","linkDescription":"Programs with product, solutions and cloud partners","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/why-snowflake/partners/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Snowflake Partner Network"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/nav_dropdown_menu_2/nav_column_container/nav_column_copy_copy/nav_item/icon.coreimg.svg/1723828498700/nav-icon--partner-network.svg","alt":"Partner Network icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy":{"id":"nav-item-4c80798317","linkDescription":"Partners, apps and solutions for enhanced deployment","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/why-snowflake/partners/all-partners/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Partner Finder"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/nav_dropdown_menu_2/nav_column_container/nav_column_copy_copy/nav_item_copy/icon.coreimg.svg/1726173927645/nav-icon--partner-finder.svg","alt":"Partner Finder icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_1970515619":{"id":"nav-item-78d12eb58f","linkDescription":"Live and virtual events","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/why-snowflake/partners/event-partnership-opportunities/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Event Partnership Opportunities"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/nav_dropdown_menu_2/nav_column_container/nav_column_copy_copy/nav_item_copy_1970515619/icon.coreimg.svg/1726173935655/nav-icon--events.svg","alt":"Calendar icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item","nav_item_copy","nav_item_copy_1970515619"]}},":itemsOrder":["nav_column","nav_column_copy","nav_column_833417450","nav_column_copy_copy"]},":type":"snowflake-site/components/nav/nav-dropdown-menu","cq:panelTitle":"Solutions"},"item_1719963657751_c":{"id":"nav-dropdown-menu-d244765b85","enableDropdown":true,"nav_column_container":{"layout":"SIMPLE","id":"container-fb6e1a59f5",":type":"snowflake-site/components/nav/nav-column/nav-column-container",":items":{"nav_column":{"numberOfSubColumns":"one-column","minWidth":"230","maxWidth":"350","layout":"SIMPLE","id":"container-319a18a6a6",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item_copy_copy_2_793631646":{"id":"nav-item-eb3ef87e0e","additionalClasses":"nav-item__platform-parent-why-sf","linkDescription":"Collaborate locally and globally to reveal new insights, create previously unforeseen business opportunities, and identify your customers with seamless experiences.","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/why-snowflake/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Why Snowflake"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item_copy_copy_2_793631646"]},"nav_column_copy_copy":{"additionalClasses":"meganav-platform-features","numberOfSubColumns":"two-columns","maxWidth":"1200","layout":"SIMPLE","id":"container-3c2b560b0b",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item":{"id":"nav-item-d5213321f3","propertiesId":"testID","linkDescription":"Case studies and videos showcasing how global organizations use Snowflake","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/customers/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Customers"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751_c/nav_column_container/nav_column_copy_copy/nav_item/icon.coreimg.svg/1739839279367/nav-icon--partner-network.svg","alt":"Customer icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_258535199":{"id":"nav-item-734ee09921","propertiesId":"workload-nav-1","linkDescription":"Learn how to connect, share and integrate the data and apps on the AI Data Cloud","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/why-snowflake/what-is-data-cloud/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"The AI Data Cloud Explained"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751_c/nav_column_container/nav_column_copy_copy/nav_item_258535199/icon.coreimg.svg/1739840490955/nav-icon-cloud.svg","alt":"Cloud icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_185565":{"id":"nav-item-6a8008e299","linkDescription":"Comprehensive security through built-in features, robust cloud infrastructure protection, and more","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/why-snowflake/snowflake-security-hub/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Security Hub"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751_c/nav_column_container/nav_column_copy_copy/nav_item_copy_185565/icon.coreimg.svg/1758909528089/user-security-admins-ciso-icon.svg","alt":"User with security lock icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy":{"id":"nav-item-faf13ec21b","additionalClasses":"is-light-gray-icon","linkDescription":"Maximize economic value through minimizing TCO and continuously optimizing price for performance","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/pricing-options/cost-and-performance-optimization/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Cost and Performance Optimization"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751_c/nav_column_container/nav_column_copy_copy/nav_item_copy/icon.coreimg.svg/1758909542267/nav-icon-cost-optimization-performance.svg","alt":"Cost Optimization icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_185565_903555964":{"id":"nav-item-026507d745","linkDescription":"Startups building applications in the AI Data Cloud","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/why-snowflake/startup-program/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Snowflake for Startups"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751_c/nav_column_container/nav_column_copy_copy/nav_item_copy_185565_903555964/icon.coreimg.svg/1758732224323/launch.svg","alt":"Launch","height":"64","width":"65",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item","nav_item_258535199","nav_item_copy_185565","nav_item_copy","nav_item_copy_185565_903555964"]}},":itemsOrder":["nav_column","nav_column_copy_copy"]},":type":"snowflake-site/components/nav/nav-dropdown-menu","cq:panelTitle":"Why Snowflake"},"item_1719961362824":{"id":"nav-dropdown-menu-672a923df6","enableDropdown":true,"nav_column_container":{"layout":"SIMPLE","id":"container-82352219c4",":type":"snowflake-site/components/nav/nav-column/nav-column-container",":items":{"nav_column_copy":{"navColumnTitle":"Connect","numberOfSubColumns":"one-column","minWidth":"124","layout":"SIMPLE","id":"container-6962ad82b9",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item":{"id":"nav-item-c2f0312a44","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/blog/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Blog"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_180298689":{"id":"nav-item-e0a671347b","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/events/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Events"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_1639361946":{"id":"nav-item-b0bac7bde6","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/support/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Support"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_680912746":{"id":"nav-item-d64b41be82","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/contact/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Contact us"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item","nav_item_180298689","nav_item_1639361946","nav_item_680912746"]},"nav_column_44600420__826130542":{"navColumnTitle":"Learn","numberOfSubColumns":"two-columns","layout":"SIMPLE","id":"container-56b3f5904a",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item_copy":{"id":"nav-item-5fe73077a5","linkDescription":"Ebooks, videos, white papers and more","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/resources/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Resource Library"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719961362824/nav_column_container/nav_column_44600420__826130542/nav_item_copy/icon.coreimg.svg/1736877128196/nav-icon--notebooks.svg","alt":"Notebooks icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item":{"id":"nav-item-56a0ba67a7","linkDescription":"Overview of Snowflake's educational offerings","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/resources/learn/training/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Training"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719961362824/nav_column_container/nav_column_44600420__826130542/nav_item/icon.coreimg.svg/1722385094416/nav-icon--training.svg","alt":"Training icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_144634_1984107859":{"id":"nav-item-c2c7eb1948","linkDescription":"Expert-led discussions and demos across industries and use cases","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/webinars/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Webinars"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719961362824/nav_column_container/nav_column_44600420__826130542/nav_item_copy_144634_1984107859/icon.coreimg.svg/1759424691990/nav-icon--webinars.svg","alt":"Webinars icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_1438098918":{"id":"nav-item-2949b14930","linkDescription":"Snowflake's technical industry professional certifications","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/en/resources/learn/certifications/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Certifications"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719961362824/nav_column_container/nav_column_44600420__826130542/nav_item_copy_1438098918/icon.coreimg.svg/1722382780833/nav-icon--cert.svg","alt":"Certification icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_143809":{"id":"nav-item-a624a6fcab","linkDescription":"Weekly product demos showcasing key features and live Q&A ","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/webinars/demo/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Live Demos"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719961362824/nav_column_container/nav_column_44600420__826130542/nav_item_copy_143809/icon.coreimg.svg/1759424359543/nav-icon--live-demo.svg","alt":"Live Demo icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_333890638":{"id":"nav-item-1f19db2ac8","linkDescription":"Training courses for all levels, on-demand or instructor-led","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"attributes":{"target":"_blank"},"url":"https://learn.snowflake.com/en/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Snowflake University"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719961362824/nav_column_container/nav_column_44600420__826130542/nav_item_copy_333890638/icon.coreimg.svg/1722382769808/nav-icon--education.svg","alt":"Education icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_189945":{"id":"nav-item-5ffd8d7749","linkDescription":"Instructor-led virtual workshops for exploring key Snowflake features","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/webinars/virtual-hands-on-lab/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Hands-On Labs"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719961362824/nav_column_container/nav_column_44600420__826130542/nav_item_copy_189945/icon.coreimg.svg/1759388182903/nav-icon--labs.svg","alt":"Hands-on Labs icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_333890":{"id":"nav-item-f19ccbed00","linkDescription":"Academic papers written by Snowflake researchers","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/resources/publications/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Snowflake Research Publications"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719961362824/nav_column_container/nav_column_44600420__826130542/nav_item_copy_333890/icon.coreimg.svg/1756326371387/copy.svg","alt":"Copy","height":"64","width":"65",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_333890_930852828":{"id":"nav-item-0b42b08971","linkDescription":"Informative articles about AI and data topics","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/fundamentals/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Fundamentals"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719961362824/nav_column_container/nav_column_44600420__826130542/nav_item_copy_333890_930852828/icon.coreimg.svg/1756853637155/data-sheet.svg","alt":"Document with list","height":"64","width":"65",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item_copy","nav_item","nav_item_copy_144634_1984107859","nav_item_copy_1438098918","nav_item_copy_143809","nav_item_copy_333890638","nav_item_copy_189945","nav_item_copy_333890","nav_item_copy_333890_930852828"]}},":itemsOrder":["nav_column_copy","nav_column_44600420__826130542"]},"nav_promo_section":{"id":"nav-promo-section-33b4650620","experience_fragment_1":{"id":"experiencefragment-33cecbabe6","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/nav-promo-card/master1/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment",":items":{"root":{"columnClassNames":{"nav_promo_card":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-b27f1f033d",":type":"snowflake-site/components/container",":items":{"nav_promo_card":{"id":"nav-promo-card-25b55b6e94","openInNewWindow":true,"layout":"horizontal","headline":"The Modern Marketing Data Stack 5th Edition","description":"AI agents are changing the marketing stack. See how to govern the shift. ","linkTitle":"Learn more","linkUrl":"/en/the-modern-marketing-data-stack-report/","image":{"id":"image","lazyEnabled":true,"src":"https://www.snowflake.com/adobe/dynamicmedia/deliver/dm-aid--b3030d24-fd50-45e6-bfe6-9520d3eb46d8/web-inside-the-mmds-5th-960x540.png?preferwebp=true&quality=85","alt":"MMDS report 5th edition","height":"540","width":"960",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-promo-card"}},":itemsOrder":["nav_promo_card"]},"cq:metadata":{":type":"nt:unstructured"}},":itemsOrder":["root","cq:metadata"],"classNames":"aem-xf"},"experience_fragment_2":{"id":"experiencefragment-6ab15e3467","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/nav-promo-card/navigation-promo-card-2/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment",":items":{"root":{"columnClassNames":{"nav_promo_card":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-fa3fcd445d",":type":"snowflake-site/components/container",":items":{"nav_promo_card":{"id":"nav-promo-card-ae8a2e08fc","openInNewWindow":true,"layout":"horizontal","headline":"The ROI of Gen AI and Agents 2026","description":"Discover how 92% of early adopters are achieving positive ROI with gen AI.","linkTitle":"Learn More","linkUrl":"/en/lp/radical-roi-generative-ai/","image":{"id":"image","lazyEnabled":true,"src":"https://www.snowflake.com/adobe/dynamicmedia/deliver/dm-aid--0c15edae-1a97-4739-8b16-c7f3941a6d9e/web-roi-of-gen-ai-and-agents-2026-r02-960x540.png?preferwebp=true&quality=85","alt":"roi of gen ai and agents","height":"540","width":"960",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-promo-card"}},":itemsOrder":["nav_promo_card"]},"cq:metadata":{":type":"nt:unstructured"}},":itemsOrder":["root","cq:metadata"],"classNames":"aem-xf"},"experience_fragment_3":{"id":"experiencefragment-4862caa8d7","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/nav-promo-card/navigation-promo-card-3/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment",":items":{"root":{"columnClassNames":{"nav_promo_card":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-9b646ece70",":type":"snowflake-site/components/container",":items":{"nav_promo_card":{"id":"nav-promo-card-2de2c15efe","openInNewWindow":true,"layout":"horizontal","headline":"Startup 2026: AI Agents Mean Business","description":"Venture leaders weigh in on agentic AI. ","linkTitle":"Learn more","linkUrl":"/en/lp/building-startup-ai-age/","image":{"id":"image","lazyEnabled":true,"src":"https://www.snowflake.com/adobe/dynamicmedia/deliver/dm-aid--a320b404-dca1-4477-b033-c79708538657/web-startup-2026-960x540.png?preferwebp=true&quality=85","alt":"alt","height":"540","width":"960",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-promo-card"}},":itemsOrder":["nav_promo_card"]},"cq:metadata":{":type":"nt:unstructured"}},":itemsOrder":["root","cq:metadata"],"classNames":"aem-xf"},":type":"snowflake-site/components/nav/nav-promo-section"},":type":"snowflake-site/components/nav/nav-dropdown-menu","cq:panelTitle":"Resources"},"item_1719963657751":{"id":"nav-dropdown-menu-f87d2698f7","enableDropdown":true,"nav_column_container":{"layout":"SIMPLE","id":"container-c239c9ef93",":type":"snowflake-site/components/nav/nav-column/nav-column-container",":items":{"nav_column_copy_copy":{"navColumnTitle":"Build","numberOfSubColumns":"one-column","layout":"SIMPLE","id":"container-9d7fafa898",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item":{"id":"nav-item-b98f2cf206","propertiesId":"testID","linkDescription":"Overview of the dev resources you need to build and scale","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/developers/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Snowflake for Developers"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751/nav_column_container/nav_column_copy_copy/nav_item/icon.coreimg.svg/1731362494574/nav-icon--devs.svg","alt":"Developers icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_1855651246":{"id":"nav-item-09b3406a34","linkDescription":"Reference architectures, use cases and best practices","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/developers/guides/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Developer Guides"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751/nav_column_container/nav_column_copy_copy/nav_item_copy_1855651246/icon.coreimg.svg/1761677891705/nav-icon--solution-center.svg","alt":"Solution Center icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy":{"id":"nav-item-150c5a9680","additionalClasses":"is-light-gray-icon","linkDescription":"The latest software versions, drivers, libraries and relevant docs","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/developers/downloads/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Downloads"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751/nav_column_container/nav_column_copy_copy/nav_item_copy/icon.coreimg.svg/1731362660050/nav-icon-download.svg","alt":"Download icon","height":"28","width":"28",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item","nav_item_copy_1855651246","nav_item_copy"]},"nav_column_copy_copy_1367930678":{"navColumnTitle":"Learn","numberOfSubColumns":"one-column","layout":"SIMPLE","id":"container-37732ad851",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item":{"id":"nav-item-93d2aad59c","propertiesId":"testID","linkDescription":"Reference docs, guides, tutorials and announcements","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"attributes":{"target":"_blank"},"url":"https://docs.snowflake.com/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Documentation"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751/nav_column_container/nav_column_copy_copy_1367930678/nav_item/icon.coreimg.svg/1731361950527/nav-icon--docs.svg","alt":"Docs icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy":{"id":"nav-item-20eff2838f","additionalClasses":"is-light-gray-icon","linkDescription":"Key projects Snowflake engineers maintain and support","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/developers/open-source/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Open Source"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751/nav_column_container/nav_column_copy_copy_1367930678/nav_item_copy/icon.coreimg.svg/1731365437016/nav-icon-open-source.svg","alt":"Open Source icon","height":"32","width":"32",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_copy":{"id":"nav-item-fca1c4672e","additionalClasses":"is-light-gray-icon","linkDescription":"Online and in-person classes and workshops to upskill on Snowflake","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"/en/developers/northstar/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_INTERNAL","text":"Builder Education"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751/nav_column_container/nav_column_copy_copy_1367930678/nav_item_copy_copy/icon.coreimg.svg/1731362475640/nav-icon--northstar.svg","alt":"Northstar logo","height":"32","width":"32",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item","nav_item_copy","nav_item_copy_copy"]},"nav_column_copy_copy_1101894776":{"navColumnTitle":"Connect","numberOfSubColumns":"one-column","layout":"SIMPLE","id":"container-40d95aec58",":type":"snowflake-site/components/nav/nav-column",":items":{"nav_item":{"id":"nav-item-9a63e5c2a5","propertiesId":"testID","linkDescription":"Snowflake’s technical leaders on what, why and how they build features","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://www.snowflake.com/engineering-blog/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Engineering Blog"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751/nav_column_container/nav_column_copy_copy_1101894776/nav_item/icon.coreimg.svg/1757101368571/nav-icon--developer-center.svg","alt":"Developers icon","height":"32","width":"32",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"},"nav_item_copy_1855651246":{"id":"nav-item-4d5d9c887f","linkDescription":"Tips, tricks and discussion with fellow Snowflake developers","button":{"id":"button","showOutboundIcon":false,"buttonLink":{"valid":true,"attributes":{"target":"_blank"},"url":"https://community.snowflake.com/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Community"},"icon":{"id":"icon","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/mega-nav-header/master/_jcr_content/root/mega_header/nav_mega/item_1719963657751/nav_column_container/nav_column_copy_copy_1101894776/nav_item_copy_1855651246/icon.coreimg.svg/1731362644348/nav-icon--partner-network.svg","alt":"Partner Network icon","height":"64","width":"64",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-item"}},":itemsOrder":["nav_item","nav_item_copy_1855651246"]}},":itemsOrder":["nav_column_copy_copy","nav_column_copy_copy_1367930678","nav_column_copy_copy_1101894776"]},"nav_promo_section":{"id":"nav-promo-section-7de9d2cdbb","experience_fragment_1":{"id":"experiencefragment-e986f48747","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/nav-promo-card/nav-promo-5/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment",":items":{"root":{"columnClassNames":{"nav_promo_card":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-141b706bc9",":type":"snowflake-site/components/container",":items":{"nav_promo_card":{"id":"nav-promo-card-8de0df0ff0","openInNewWindow":false,"layout":"horizontal","headline":"Get started with your first Snowflake Notebook","description":"Write and execute code, visualize results, and tell the story of your analysis all in one place.","linkTitle":"Learn More","linkUrl":"/en/developers/solutions-center/getting-started-with-your-first-snowflake-notebook-project/","image":{"id":"image","lazyEnabled":true,"src":"https://www.snowflake.com/adobe/dynamicmedia/deliver/dm-aid--dc7e334a-c38b-4283-b1de-fcf829952eef/nav-promo-first-notebook.jpg?preferwebp=true&quality=85","alt":"alt","height":"210","width":"415",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-promo-card"}},":itemsOrder":["nav_promo_card"]},"cq:LiveSyncConfig":{"cq:isDeep":true,"cq:rolloutConfigs":[],"cq:master":"/content/experience-fragments/snowflake-site/language-masters/en/site/nav-promo-card/nav-promo-card-4",":type":"cq:LiveCopy"}},":itemsOrder":["root","cq:LiveSyncConfig"],"classNames":"aem-xf"},"experience_fragment_2":{"id":"experiencefragment-25355fba39","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/nav-promo-card/nav-promo-card-4/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment",":items":{"root":{"columnClassNames":{"nav_promo_card":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-73ff971854",":type":"snowflake-site/components/container",":items":{"nav_promo_card":{"id":"nav-promo-card-021dd682f4","openInNewWindow":true,"layout":"horizontal","headline":"Northstar Builder Workshops","description":"Join other developers as you roll up your sleeves and explore the possibilities of Snowflake.","linkTitle":"Learn More","linkUrl":"/en/nav-promos/northstar-builders-workshop/","image":{"id":"image","lazyEnabled":true,"src":"https://www.snowflake.com/adobe/dynamicmedia/deliver/dm-aid--14341ced-bc5e-4a29-9762-b7857f6cadfc/nav-promo-northstar.jpg?preferwebp=true&quality=85","alt":"Snowflake Northstar logo","height":"700","width":"1440",":type":"snowflake-site/components/image"},":type":"snowflake-site/components/nav/nav-promo-card"}},":itemsOrder":["nav_promo_card"]},"cq:LiveSyncConfig":{"cq:isDeep":true,"cq:rolloutConfigs":[],"cq:master":"/content/experience-fragments/snowflake-site/language-masters/en/site/nav-promo-card/master",":type":"cq:LiveCopy"}},":itemsOrder":["root","cq:LiveSyncConfig"],"classNames":"aem-xf"},":type":"snowflake-site/components/nav/nav-promo-section"},":type":"snowflake-site/components/nav/nav-dropdown-menu","cq:panelTitle":"Developers"},"item_1718247180324":{"id":"nav-dropdown-menu-c41024bf2d","enableDropdown":false,"link_url":"/en/pricing-options/",":type":"snowflake-site/components/nav/nav-dropdown-menu","cq:panelTitle":"Pricing"}},":itemsOrder":["item_1719963657751_c_663444255","nav_dropdown_menu_2","item_1719963657751_c","item_1719961362824","item_1719963657751","item_1718247180324"]},"languagenavigation":{"id":"language-navigation-f7ef614920","languageNavItems":[{"title":"English","path":"/en/developers/guides/geo-for-machine-learning/","locale":"en","active":true},{"title":"日本語","path":"/ja/","locale":"ja","active":false},{"title":"한국어","path":"/ko/","locale":"ko","active":false},{"title":"中文（简体）","path":"/zh_cn/","locale":"zh-cn","active":false},{"title":"Português","path":"/pt_br/","locale":"pt-br","active":false},{"title":"Deutsch","path":"/de/","locale":"de","active":false},{"title":"Français","path":"/fr/","locale":"fr","active":false},{"title":"Español","path":"/es/","locale":"es","active":false},{"title":"Italiano","path":"/it/","locale":"it","active":false}],":type":"snowflake-site/components/nav/language-navigation"},"button_1177328691":{"id":"button-6282088bfe","heapButtonClasses":["mega-nav__sign-in"],"showOutboundIcon":false,"buttonLink":{"valid":true,"attributes":{"target":"_blank"},"url":"https://app.snowflake.com/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","appliedCssClassNames":"snowflake-button-link snowflake-button-black snowflake-button-compact","linkType":"SNOWFLAKE_EXTERNAL","text":"Sign in"},"button":{"id":"button-66f5f1ce8c","heapButtonClasses":["contact_nav","heap-nav-contact"],"showOutboundIcon":true,"buttonLink":{"valid":true,"url":"/en/contact-sales/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","appliedCssClassNames":"snowflake-button-secondary snowflake-button-blue snowflake-button-compact","linkType":"SNOWFLAKE_INTERNAL","text":"CONTACT SALES"},"button_288358396":{"id":"button-104da2a081","heapButtonClasses":["start_for_free_nav","heap-nav-start-for-free"],"showOutboundIcon":false,"buttonLink":{"valid":true,"url":"https://signup.snowflake.com/"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","appliedCssClassNames":"snowflake-button-primary snowflake-button-blue snowflake-button-compact","linkType":"SNOWFLAKE_EXTERNAL","text":"start for free"}},":itemsOrder":["nav_mega","languagenavigation","button_1177328691","button","button_288358396"],"appliedCssClassNames":"snowflake-header-container white"}},":itemsOrder":["markup_editor","mega_header"]},"image":{":type":"nt:unstructured"},"cq:targetMetadata":{"cq:targetStatus":"OUT_OF_SYNC","cq:exportTime":1781280015540,"cq:targetOfferId":860250,":type":"nt:unstructured"}},":itemsOrder":["root","image","cq:targetMetadata"],"classNames":"aem-xf"},"markup_editor_1950346551":{"id":"markup-editor-1379bbfdc7","title":" ","cssContent":".snowflake-markdown-table code[class*=language-],.snowflake-markdown-table code[class*=language-],.snowflake-markdown .snowflake-text code[class*=language-],.snowflake-markdown .snowflake-text pre[class*=language-]{background-color:rgba(var(--ui-12-rgb),.5);color:var(--text-01);text-shadow:none;padding:var(--spacing-00);border-radius:var(--spacing-00);font-size:smaller}","isGSAPEnabled":false,":type":"snowflake-site/components/markup-editor"},"responsivegrid":{"columnClassNames":{"quickstart_hero":"aem-GridColumn aem-GridColumn--default--12","flexible_column_cont":"aem-GridColumn aem-GridColumn--default--12","markup_editor":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnCount":12,":items":{"quickstart_hero":{"id":"quickstart-hero-0eac832c01","isDeveloperGuidesPage":false,"quickstartHeroFirstCertifiedTag":{"tagText":"Quickstart","tagColor":"#29B5E8","tagPath":"/content/cq:tags/snowflake-site/taxonomy/solution-center/certification/quickstart","tagIcon":""},"quickstartHeroTitle":{"lines":["Geospatial Analytics, AI and ML using Snowflake"],"type":"heading2",":type":"snowflake-site/components/title-v2"},"quickstartHeroAuthor":"Oleksii Bielov","quickstartHeroFirstSnowflakeFeatureTag":{"tagText":"Unstructured Data Analysis","tagColor":"#29B5E8","tagPath":"/content/cq:tags/snowflake-site/taxonomy/snowflake-feature/unstructured-data-analysis","tagIcon":""},"quickstartHeroForkRepoLink":{"id":"button-104aeb75b9","showOutboundIcon":false,"buttonLink":{"valid":true,"attributes":{"target":"_blank"},"url":"https://github.com/Snowflake-Labs/sfquickstarts/tree/master/site/sfguides/src/geo-for-machine-learning"},"linkTargetContentType":"GENERIC",":type":"snowflake-site/components/button","linkType":"SNOWFLAKE_EXTERNAL","text":"Fork Repo"},"quickstartHeroBreadcrumbs":[{"title":"Geospatial Analytics, AI and ML using Snowflake","url":"https://www.snowflake.com/content/snowflake-site/global/en/developers/guides/geo-for-machine-learning","currentPage":true},{"title":"Guides","url":"https://www.snowflake.com/content/snowflake-site/global/en/developers/guides","currentPage":false},{"title":"Snowflake for Developers","url":"https://www.snowflake.com/content/snowflake-site/global/en/developers","currentPage":false}],"fragmentPath":"/content/dam/snowflake-site/en/content-fragments/quickstarts/geo-for-machine-learning",":type":"snowflake-site/components/quickstart/quickstart-hero"},"flexible_column_cont":{"id":"flexible-column-container-db3c8c396a","propertiesId":"quickstart-template-main-flexible-container","type":"2-column-75-25","alignColumns":"top","containerMaxWidth":"extra-large","topPadding":"none","bottomPadding":"none","spaceBetween":"small","reverseOnMobile":false,"carouselOnMobile":false,"backgroundImageOption":"none","flexible_column_content_container_1":{"layout":"SIMPLE","id":"container-5067b48ec1",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"contentfragment":{"id":"contentfragment-2260a7aeeb","paragraphs":["&lt;!-- ----------------------------------------- --&gt;\n","\u003Ch2\u003EOverview\u003C/h2\u003E\n","\u003Cp\u003ESnowflake offers a rich toolkit for predictive analytics with a geospatial component. It includes two data types and specialized functions for transformation, prediction, and visualization. This guide is divided into multiple labs, each covering a separate use case that showcases different features for a real-world scenario.\u003C/p\u003E\n","\u003Ch3\u003EPrerequisites\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003EUnderstanding of \u003Ca href=\"/blog/getting-started-with-h3-hexagonal-grid/\"\u003EDiscrete Global Grid H3\u003C/a\u003E\u003C/li\u003E\u003Cli\u003ERecommend: Understanding of \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/data-types-geospatial\"\u003EGeospatial Data Types\u003C/a\u003E and \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions-geospatial\"\u003EGeospatial Functions\u003C/a\u003E in Snowflake\u003C/li\u003E\u003Cli\u003ERecommended: Complete \u003Ca href=\"/en/developers/guides/geo-analysis-geometry/\"\u003EGeospatial Analysis using Geometry and Geography Data Types\u003C/a\u003E quickstart\u003C/li\u003E\u003Cli\u003ERecommended: Complete \u003Ca href=\"/en/developers/guides/geo-performance/\"\u003EPerformance Optimization Techniques for Geospatial queries\u003C/a\u003E quickstart\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003EWhat You&rsquo;ll Learn\u003C/h3\u003E\n","\u003Cp\u003EIn this quickstart, you will use H3, Time Series, Cortex ML and Streamlit for ML use cases. The quickstart is broken up into separate labs:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ELab 1: Geospatial 101\u003C/li\u003E\u003Cli\u003ELab 2: Energy grids analysis using GEOMETRY\u003C/li\u003E\u003Cli\u003ELab 3: Geocoding and Reverse Geocoding\u003C/li\u003E\u003Cli\u003ELab 4: Forecasting time series on a map\u003C/li\u003E\u003Cli\u003ELab 5: Sentiment analysis of customer reviews\u003C/li\u003E\u003Cli\u003ELab 6: Processing unstructured geospatial data\u003C/li\u003E\u003Cli\u003ELab 7: Creating Interactive Maps with Kepler.gl\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EWhen you complete this quickstart, you will have gained practical experience in several areas:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EAcquiring data from the Snowflake Marketplace\u003C/li\u003E\u003Cli\u003ELoading data from external storage\u003C/li\u003E\u003Cli\u003ETransforming data using H3 and Time Series functions\u003C/li\u003E\u003Cli\u003ETraining models and predicting results with Cortex ML\u003C/li\u003E\u003Cli\u003EUsing LLM for analysing textual data\u003C/li\u003E\u003Cli\u003EVisualizing data with Streamlit\u003C/li\u003E\u003Cli\u003EProcessing unstructured geospaial data (GeoTIFF, Shapefiles)\u003C/li\u003E\u003Cli\u003EUsing geo visualisation apps available in Marketplace\u003C/li\u003E\u003Cli\u003EProcessing unstructured geospaial data (GeoTIFF, Shapefiles)\u003C/li\u003E\u003Cli\u003EUsing geo visualisation apps available in Marketplace\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003EWhat You&rsquo;ll Need\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003EA supported Snowflake \u003Ca href=\"https://docs.snowflake.com/en/user-guide/setup.html\"\u003EBrowser\u003C/a\u003E\u003C/li\u003E\u003Cli\u003ESign-up for a \u003Ca href=\"https://signup.snowflake.com/?utm_source=snowflake-devrel&amp;utm_medium=developer-guides&amp;utm_cta=developer-guides\"\u003ESnowflake Trial\u003C/a\u003E OR have access to an existing Snowflake account with the \u003Ccode\u003EACCOUNTADMIN\u003C/code\u003E role or the \u003Ccode\u003EIMPORT SHARE\u003C/code\u003Eprivilege. Select the Enterprise edition, AWS as a cloud provider and US East (Northern Virginia) or EU (Frankfurt) as a region.\u003C/li\u003E\u003C/ul\u003E\n&lt;!-- ----------------------------------------- --&gt;\n","\u003Ch2\u003ESetup your Account\u003C/h2\u003E\n","\u003Cp\u003EIf this is the first time you are logging into the Snowflake UI, you will be prompted to enter your account name or account URL that you were given when you acquired a trial. The account URL contains your \u003Ca href=\"https://docs.snowflake.com/en/user-guide/connecting.html#your-snowflake-account-name\"\u003Eaccount name\u003C/a\u003E and potentially the region. You can find your account URL in the email that was sent to you after you signed up for the trial.\u003C/p\u003E\n","\u003Cp\u003EClick \u003Ccode\u003ESign-in\u003C/code\u003E and you will be prompted for your username and password.\u003C/p\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EIf this is not the first time you are logging into the Snowflake UI, you should see a &quot;Select an account to sign into&quot; prompt and a button for your account name listed below it. Click the account you wish to access and you will be prompted for your user name and password (or another authentication mechanism).\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Ch3\u003EIncrease Your Account Permission\u003C/h3\u003E\n","\u003Cp\u003EThe Snowflake web interface has a lot to offer, but for now, switch your current role from the default \u003Ccode\u003ESYSADMIN\u003C/code\u003E to \u003Ccode\u003EACCOUNTADMIN\u003C/code\u003E. This increase in permissions will allow you to create shared databases from Snowflake Marketplace listings.\u003C/p\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EIf you don't have the \u003Ccode\u003EACCOUNTADMIN\u003C/code\u003E role, switch to a role with \u003Ccode\u003EIMPORT SHARE\u003C/code\u003E privileges instead.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_1.png\" alt=\"assets/geo_ml_1.png\"\u003E\u003C/p\u003E\n","\u003Ch3\u003ECreate a Virtual Warehouse\u003C/h3\u003E\n","\u003Cp\u003EYou will need to create a Virtual Warehouse to run queries.\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ENavigate to the \u003Ccode\u003EAdmin &gt; Warehouses\u003C/code\u003E screen using the menu on the left side of the window\u003C/li\u003E\u003Cli\u003EClick the big blue \u003Ccode\u003E+ Warehouse\u003C/code\u003E button in the upper right of the window\u003C/li\u003E\u003Cli\u003ECreate a LARGE Warehouse as shown in the screen below\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_2.png\" alt=\"assets/geo_ml_2.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EBe sure to change the \u003Ccode\u003ESuspend After (min)\u003C/code\u003E field to 5 min to avoid wasting compute credits.\u003C/p\u003E\n","\u003Cp\u003ENavigate to the query editor by clicking on \u003Ccode\u003EWorksheets\u003C/code\u003E on the top left navigation bar and choose your warehouse.\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EClick the + Worksheet button in the upper right of your browser window. This will open a new window.\u003C/li\u003E\u003Cli\u003EIn the new Window, make sure \u003Ccode\u003EACCOUNTADMIN\u003C/code\u003E and \u003Ccode\u003EMY_WH\u003C/code\u003E (or whatever your warehouse is named) are selected in the upper right of your browser window.\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_3.png\" alt=\"assets/geo_ml_3.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ECreate a new database and schema where you will store datasets in the \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E data type. Copy &amp; paste the SQL below into your worksheet editor, put your cursor somewhere in the text of the query you want to run (usually the beginning or end), and either click the blue &quot;Play&quot; button in the upper right of your browser window, or press \u003Ccode\u003ECTRL+Enter\u003C/code\u003E or \u003Ccode\u003ECMD+Enter\u003C/code\u003E (Windows or Mac) to run the query.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE DATABASE IF NOT EXISTS advanced_analytics;\n// Set the working database schema\nUSE ADVANCED_ANALYTICS.PUBLIC;\nUSE WAREHOUSE my_wh;\nALTER SESSION SET GEOGRAPHY_OUTPUT_FORMAT='WKT';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch2\u003EGeospatial 101\u003C/h2\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EBefore starting with this lab, complete the preparation steps from \u003Ccode\u003ESetup your account\u003C/code\u003E page.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EThis lab is \u003Ca href=\"https://github.com/Snowflake-Labs/sf-guide-geospatial-analytics-ai-ml\"\u003Eavailable\u003C/a\u003E as Snowflake Notebook.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Ch3\u003E1. Overview\u003C/h3\u003E\n","\u003Cp\u003EGeospatial query capabilities in Snowflake are built upon a combination of data types and specialized query functions that can be used to parse, construct, and perform calculations on geospatial objects. Additionally, geospatial data can be visualized in Snowflake using Streamlit. This guide provides an entry-level introduction to geospatial analytics and visualization in Snowflake. In this lab, you will explore a sample use case of identifying the closest healthcare facilities near a geographic point, and you will learn:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EHow to view the GEOGRAPHY data type with supported formats\u003C/li\u003E\u003Cli\u003EHow to construct a geospatial object from latitude and longitude values\u003C/li\u003E\u003Cli\u003EHow to extract latitude and longitude from a geography column\u003C/li\u003E\u003Cli\u003EHow to perform geospatial calculations and filtering\u003C/li\u003E\u003Cli\u003EHow to visualize geospatial data using Streamlit in Snowflake\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003E2. Acquire Data\u003C/h3\u003E\n","\u003Cp\u003EFor this lab oyu will use \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9KR/carto-overture-maps-places\"\u003EOverture Maps - Places\u003C/a\u003E dataset from Marketplace. Now you can acquire sample geospatial data from the Snowflake Marketplace.\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ENavigate to the Marketplace screen using the menu on the left side of the window\u003C/li\u003E\u003Cli\u003ESearch for \u003Ccode\u003EOverture Maps\u003C/code\u003E in the search bar\u003C/li\u003E\u003Cli\u003EFind and click the \u003Ccode\u003EOverture Maps - Places\u003C/code\u003E tile\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EOn the Get Data screen, keep the default database name OVERTURE_MAPS__PLACES, as all of the future instructions will assume this name for the database.\u003C/p\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EOn the \u003Ccode\u003EGet\u003C/code\u003E screen, you may be prompted to complete your \u003Ccode\u003Euser profile\u003C/code\u003E if you have not done so before. Click the link as shown in the screenshot below. Enter your name and email address into the profile screen and click the blue \u003Ccode\u003ESave\u003C/code\u003E button. You will be returned to the \u003Ccode\u003EGet\u003C/code\u003E screen.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003ECongratulations! You have just created a shared database from a listing on the Snowflake Marketplace.\u003C/p\u003E\n","\u003Cp\u003EAs one additional preparation step you need to complete is to import libraries that you will use in this Lab, navigate to the \u003Ccode\u003EPackages\u003C/code\u003E drop-down  in the upper right of the Notebook and search for \u003Ccode\u003Epydeck\u003C/code\u003E. Click on \u003Ccode\u003Epydeck\u003C/code\u003E to add it to the Python packages.\u003C/p\u003E\n","\u003Ch3\u003E3. Understanding Snowflake Geospatial Formats\u003C/h3\u003E\n","\u003Cp\u003ESnowflake supports GeoJSON, Well-Known Text (WKT) and Well-Known Binary (WKB) formats for loading and unloading geospatial data. You can use session or account parameters to control which of these format types is used to display geospatial data in your query results.\u003C/p\u003E\n","\u003Cp\u003ERun the query below to explicitly set your geography output format to JSON.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EALTER SESSION SET GEOGRAPHY_OUTPUT_FORMAT = 'GEOJSON';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIn the following two queries you will familiarize yourself with \u003Ccode\u003EOverture Maps - Points of Interest\u003C/code\u003E data. First, check the size of the table:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT COUNT(*) FROM OVERTURE_MAPS__PLACES.CARTO.PLACE;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIn the following query, you will examine a geography column containing data on health and medical facilities.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT \n     NAMES['primary']::STRING AS NAME,\n     ADDRESS.value:element:locality::STRING AS CITY,\n     ADDRESS.value:element:region::STRING AS STATE,\n     ADDRESS.value:element:postcode::STRING AS POSTCODE,\n     ADDRESS.value:element:country::STRING AS COUNTRY,\n     GEOMETRY\nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE,\nLATERAL FLATTEN(INPUT =&gt; ADDRESSES:list) AS ADDRESS\nWHERE CATEGORIES['primary'] ='health_and_medical'\nLIMIT 100;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_40.png\" alt=\"assets/geo_ml_40.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ENote that while the column is named \u003Ccode\u003EGEOMETRY\u003C/code\u003E in this data source, it is stored in a \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E column in Snowflake, using the coordinate system \u003Ca href=\"https://epsg.io/4326\"\u003EESPG:4326\u003C/a\u003E, also known as \u003Ca href=\"https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84\"\u003EWGS 84\u003C/a\u003E. This coordinate system uses latitude and longitude as coordinates and is the most widely used coordinate system worldwide. If you are storing geospatial data using latitude and longitude, then the \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E data type is the most suitable for storing your data.\u003C/p\u003E\n","\u003Cp\u003EThe contents of the \u003Ccode\u003EGEOMETRY\u003C/code\u003E column in the output above, formatted as GeoJSON.\u003C/p\u003E\n","\u003Cp\u003ERun the code below to update your session geography output format to \u003Ca href=\"https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry\"\u003EWell-Known Text (WKT)\u003C/a\u003E, which is arguably more readable.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EALTER SESSION SET GEOGRAPHY_OUTPUT_FORMAT = 'WKT';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow rerun the Overture maps query. Notice how the contents of the \u003Ccode\u003EGEOMETRY\u003C/code\u003E column are displayed.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT \n     NAMES['primary']::STRING AS NAME,\n     ADDRESS.value:element:locality::STRING AS CITY,\n     ADDRESS.value:element:region::STRING AS STATE,\n     ADDRESS.value:element:postcode::STRING AS POSTCODE,\n     ADDRESS.value:element:country::STRING AS COUNTRY,\n     geometry\nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE,\nLATERAL FLATTEN(INPUT =&gt; ADDRESSES:list) AS ADDRESS\nWHERE CATEGORIES['primary'] ='health_and_medical'\nLIMIT 100;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_41.png\" alt=\"assets/geo_ml_41.png\"\u003E\u003C/p\u003E\n","\u003Ch3\u003EConstructing geospatial objects\u003C/h3\u003E\n","\u003Cp\u003EYou can use constructor functions such as \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions/st_makepoint\"\u003EST_MAKEPOINT\u003C/a\u003E, \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions/st_makeline\"\u003EST_MAKELINE\u003C/a\u003E and \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions/st_makepolygon\"\u003EST_POLYGON\u003C/a\u003E to create geospatial objects. Run the code below to create a geo point from latitude and longitude.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT ST_MAKEPOINT(-74.0266511, 40.6346599) GEO_POINT\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_42.png\" alt=\"assets/geo_ml_42.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EAlternatively, you can use the TO_GEOGRAPHY constructor function to create geospatial values. \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions/to_geography\"\u003ETO_GEOGRAPHY\u003C/a\u003E is a general purpose constructor where \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions/st_makepoint\"\u003EST_MAKEPOINT\u003C/a\u003E specifically makes a POINT object. Run the code below:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT TO_GEOGRAPHY('POINT(-74.0266511 40.6346599)') GEO_POINT\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_42.png\" alt=\"assets/geo_ml_42.png\"\u003E\u003C/p\u003E\n","\u003Ch3\u003E4. Visualizing spatial data in Streamlit\u003C/h3\u003E\n","\u003Cp\u003EUsing Streamlit, you can visualize your data using tools like \u003Ccode\u003Est.map\u003C/code\u003E or popular python packages like \u003Ccode\u003Epydeck\u003C/code\u003E.\u003C/p\u003E\n","\u003Cp\u003EAdd a new Python cell and run the code below to see how you can use \u003Ccode\u003Est.map\u003C/code\u003E to show a point on a map.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Eimport streamlit as st\nimport pandas as pd\n\n# Define the coordinates for the point\nlatitude = 40.755702\nlongitude = -73.986226\n\n# Create a DataFrame with the point\ndata = pd.DataFrame({\n    'lat': [latitude],\n    'lon': [longitude]\n})\n\n# Display the map with the point\nst.title(&quot;Display a Points with st.map&quot;)\nst.map(data)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_43.png\" alt=\"assets/geo_ml_43.png\"\u003E\u003C/p\u003E\n","\u003Ch3\u003EAccessing coordinates of a geospatial object\u003C/h3\u003E\n","\u003Cp\u003ESometimes you need to do the opposite - access individual coordinates in a geospatial object. You can do that with accessor functions \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions/st_x\"\u003EST_X\u003C/a\u003E and \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions/st_y\"\u003EST_Y\u003C/a\u003E to access longitude and latitude accordingly. Run the code below:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT \n     NAMES['primary']::STRING AS NAME,\n     ST_X(GEOMETRY) AS LONGITUDE,\n     ST_Y(GEOMETRY) AS LATITUDE,\nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE,\nLATERAL FLATTEN(INPUT =&gt; ADDRESSES:list) AS ADDRESS\nWHERE CATEGORIES['primary'] ='health_and_medical'\nLIMIT 100;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_44.png\" alt=\"assets/geo_ml_44.png\"\u003E\u003C/p\u003E\n","\u003Ch3\u003EFinding the nearest points and calculating distances\u003C/h3\u003E\n","\u003Cp\u003EYou can use relationship and measurement functions to perform spatial joins and other analytical operations. For example, you can use \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions/st_dwithin\"\u003EST_DWITHIN\u003C/a\u003E to find health facilities that are within a mile from you, and you can use \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions/st_distance\"\u003EST_DISTANCE\u003C/a\u003E to measure the actual distance between points.\u003C/p\u003E\n","\u003Cp\u003ERun the code below to obtain the ten nearest health facilities that are no more than approximately a mile (1,600 meters) away from a given point. The records are sorted by distance.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT \n     NAMES['primary']::STRING AS NAME,\n     ST_X(GEOMETRY) AS LONGITUDE,\n     ST_Y(GEOMETRY) AS LATITUDE,\n     GEOMETRY,\n     ST_DISTANCE(GEOMETRY,TO_GEOGRAPHY('POINT(-73.986226 40.755702)'))::NUMBER(6,2) \n        AS DISTANCE_METERS \nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE\nWHERE CATEGORIES['primary'] ='health_and_medical' AND\nST_DWITHIN(GEOMETRY,ST_MAKEPOINT(-73.986226, 40.755702),1600) = TRUE \nORDER BY 5 LIMIT 10;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_45.png\" alt=\"assets/geo_ml_45.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ENotice that this query runs on a table with over 53M rows. Snowflake's geospatial data types are very efficient!\u003C/p\u003E\n","\u003Ch1\u003ECreating multi-layered maps in Streamlit\u003C/h1\u003E\n","\u003Cp\u003EUsing Streamlit and Pydeck, you can create a multi-layered visualization.\u003C/p\u003E\n","\u003Cp\u003ETake note of the name of your previous cell and run the command below in a python cell to convert the results of the previous query into a pandas dataframe. We will reference this dataframe in the visualization.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Edf = query_9.to_pandas()\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you will visualize the top 10 health facilities relative to the reference point. Pydeck supports multi-layered maps that can be customized with tooltips and other features.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Eimport streamlit as st\nimport pandas as pd\nimport pydeck as pdk\n\n# Define the coordinates for your specific location\nlatitude = 40.755702\nlongitude = -73.986226\n\n# Create a DataFrame for your location\nmy_location_df = pd.DataFrame({\n    'lat': [latitude],\n    'lon': [longitude]\n})\n\n# Create a PyDeck Layer for visualizing points with larger size and a tooltip for NAME\ndata_layer = pdk.Layer(\n    &quot;ScatterplotLayer&quot;,\n    df,\n    get_position='[LONGITUDE, LATITUDE]',\n    get_radius=50,  # Adjust this value for larger points\n    get_fill_color='[255, 0, 0, 160]',  # Red color with transparency\n    pickable=True,\n    get_tooltip=['NAME'],  # Add NAME as a tooltip\n)\n\n# Create a PyDeck Layer for your location with a different color and size\nmy_location_layer = pdk.Layer(\n    &quot;ScatterplotLayer&quot;,\n    my_location_df,\n    get_position='[lon, lat]',\n    get_radius=100,  # Larger radius to highlight your location\n    get_fill_color='[0, 0, 255, 200]',  # Blue color with transparency\n    pickable=True,\n)\n\n# Set the view on the map\nview_state = pdk.ViewState(\n    latitude=df['LATITUDE'].mean(),\n    longitude=df['LONGITUDE'].mean(),\n    zoom=13.5,  # Adjust zoom if needed\n    pitch=0,\n)\n\n# Define the tooltip\ntooltip = {\n    &quot;html&quot;: &quot;&lt;b&gt;Facility Name:&lt;/b&gt; {NAME}&quot;,\n    &quot;style&quot;: {&quot;color&quot;: &quot;white&quot;}\n}\n\n# Render the map with both layers and tooltip\nr = pdk.Deck(\n    layers=[data_layer, my_location_layer],\n    initial_view_state=view_state,\n    map_style='mapbox://styles/mapbox/light-v10',\n    tooltip=tooltip\n)\n\nst.write('10 Nearest Health Facilities')\nst.pydeck_chart(r, use_container_width=True)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_46.png\" alt=\"assets/geo_ml_46.png\"\u003E\u003C/p\u003E\n","\u003Ch3\u003EConclusion\u003C/h3\u003E\n","\u003Cp\u003ECongratulations! You have completed this introductory quickstart. You learn basic operations to construct, process and visualise geospatial data.\u003C/p\u003E\n","\u003Ch2\u003EEnergy grids analysis using GEOMETRY\u003C/h2\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EBefore starting with this lab, complete the preparation steps from \u003Ccode\u003ESetup your account\u003C/code\u003E page.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EThis lab is \u003Ca href=\"https://github.com/Snowflake-Labs/sf-guide-geospatial-analytics-ai-ml\"\u003Eavailable\u003C/a\u003E as Snowflake Notebook.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Ch4\u003EOverview\u003C/h4\u003E\n","\u003Cp\u003EGeospatial query capabilities in Snowflake are built upon a combination of data types and specialized query functions that can be used to parse, construct, and run calculations over geospatial objects. This guide will introduce you to the \u003Ccode\u003EGEOMETRY\u003C/code\u003E data type, help you understand geospatial formats supported by Snowflake and walk you through the use of a variety of functions on sample geospatial data sets.\u003C/p\u003E\n","\u003Ch4\u003EWhat You&rsquo;ll Learn\u003C/h4\u003E\n\u003Cul\u003E\u003Cli\u003EHow to acquire geospatial data from the Snowflake Marketplace\u003C/li\u003E\u003Cli\u003EHow to load geospatial data from a Stage\u003C/li\u003E\u003Cli\u003EHow to interpret the \u003Ccode\u003EGEOMETRY\u003C/code\u003E data type and how it differs from the \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E\u003C/li\u003E\u003Cli\u003EHow to understand the different formats that \u003Ccode\u003EGEOMETRY\u003C/code\u003E can be expressed in\u003C/li\u003E\u003Cli\u003EHow to do spatial analysis using the \u003Ccode\u003EGEOMETRY\u003C/code\u003E and \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E data types\u003C/li\u003E\u003Cli\u003EHow to use Python UDFs for reading Shapefiles and creating custom functions\u003C/li\u003E\u003Cli\u003EHow to visualise geospatial data using Streamlit\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch4\u003EWhat You&rsquo;ll Build\u003C/h4\u003E\n","\u003Cp\u003EA sample use case that involves energy grids and LTE cell towers in the Netherlands You will answer the following questions:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EWhat is the length of all energy grids in each municipality in the Netherlands?\u003C/li\u003E\u003Cli\u003EWhat cell towers lack electricity cables nearby?\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003EAcquire Marketplace Data and Analytics Toolbox\u003C/h3\u003E\n","\u003Cp\u003EThe first step in the guide is to acquire geospatial data sets that you can freely use to explore the basics of Snowflake's geospatial functionality.  The best place to acquire this data is the Snowflake Marketplace!\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ENavigate to the \u003Ccode\u003EMarketplace\u003C/code\u003E screen using the menu on the left side of the window\u003C/li\u003E\u003Cli\u003ESearch for \u003Ccode\u003EOpenCelliD\u003C/code\u003E in the search bar\u003C/li\u003E\u003Cli\u003EFind and click the\u003Ccode\u003EOpenCelliD - Open Database of Cell Towers\u003C/code\u003E tile or just use \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZSVZ8ON6J/dataconsulting-pl-opencellid-open-database-of-cell-towers\"\u003Ethis\u003C/a\u003E link\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_47.png\" alt=\"assets/geo_ml_47.png\"\u003E\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EOnce in the listing, click the big blue \u003Ccode\u003EGet\u003C/code\u003E button\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_48.png\" alt=\"assets/geo_ml_48.png\"\u003E\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EOn the \u003Ccode\u003EGet Data\u003C/code\u003E screen, change the name of the database from the default to \u003Ccode\u003EOPENCELLID\u003C/code\u003E, as this name is shorter, and all future instructions will assume this name for the database.\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_49.png\" alt=\"assets/geo_ml_49.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ESimilarly to the above dataset, acquire \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZTYZF0RTY3/wherobots-sedonasnow\"\u003ESedonaSnow\u003C/a\u003E application which extends Snowflake core geo features with more than 100 spatial functions. Navigate to the \u003Ccode\u003EMarketplace\u003C/code\u003E screen using the menu on the left side of the window and find the \u003Ccode\u003ESedonaSnow\u003C/code\u003E. Keep the the database name \u003Ccode\u003ESEDONASNOW\u003C/code\u003E and optionally add more roles that can access the database.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_22.png\" alt=\"assets/geo_ml_22.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ECongratulations! You have just acquired all the listings you need for this lab.\u003C/p\u003E\n","\u003Ch3\u003ESetup your Account\u003C/h3\u003E\n","\u003Cp\u003ECreate a new database and schema where you will store datasets in the \u003Ccode\u003EGEOMETRY\u003C/code\u003E data type. Run th following SQL:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE DATABASE IF NOT EXISTS GEOLAB;\nCREATE SCHEMA IF NOT EXISTS GEOLAB.GEOMETRY;\nUSE SCHEMA GEOLAB.GEOMETRY;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003ELoad Data from External Storage\u003C/h3\u003E\n","\u003Cp\u003EYou already understand how to get data from Marketplace, let's try another way of getting data, namely, getting it from the external S3 storage. While you loading data you will learn formats supported by geospatial data types.\u003C/p\u003E\n","\u003Cp\u003EFor this quickstart we have prepared a dataset with energy grid infrastructure (cable lines) in the Netherlands. It is stored in the CSV format in the public S3 bucket. To import this data, create an external stage using the following SQL command:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE STAGE geolab.geometry.geostage\nURL = 's3://sfquickstarts/vhol_spatial_analysis_geometry_geography/';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you will create a new table using the file from that stage. Run the following queries to create a new file format and a new table using the dataset stored in the Stage:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003E// Create file format\nCREATE OR REPLACE FILE FORMAT geocsv TYPE = CSV SKIP_HEADER = 1 FIELD_OPTIONALLY_ENCLOSED_BY = '&quot;';\n\nCREATE OR REPLACE TABLE geolab.geometry.nl_cables_stations AS \nSELECT to_geometry($1) AS geometry, \n       $2 AS id, \n       $3 AS type \nFROM @geostage/nl_stations_cables.csv (file_format =&gt; 'geocsv');\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ELook at the description of the table you just created by running the following queries:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EDESC TABLE geolab.geometry.nl_cables_stations;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThe \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/sql/desc.html\"\u003Edesc or describe\u003C/a\u003E command shows you the definition of the view, including the columns, their data type, and other relevant details. Notice the \u003Ccode\u003Egeometry\u003C/code\u003E column is defined as \u003Ccode\u003EGEOMETRY\u003C/code\u003E type.\u003C/p\u003E\n","\u003Cp\u003ESnowflake supports 3 primary geospatial formats and 2 additional variations on those formats. They are:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003E\u003Cstrong\u003EGeoJSON\u003C/strong\u003E: a JSON-based standard for representing geospatial data\u003C/li\u003E\u003Cli\u003E\u003Cstrong\u003EWKT &amp; EWKT\u003C/strong\u003E: a &quot;Well Known Text&quot; string format for representing geospatial data and the &quot;Extended&quot; variation of that format\u003C/li\u003E\u003Cli\u003E\u003Cstrong\u003EWKB &amp; EWKB:\u003C/strong\u003E a &quot;Well Known Binary&quot; format for representing geospatial data in binary and the &quot;Extended&quot; variation of that format\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EThese formats are supported for ingestion (files containing those formats can be loaded into a \u003Ccode\u003EGEOMETRY\u003C/code\u003E typed column), query result display, and data unloading to new files. You don't need to worry about how Snowflake stores the data under the covers but rather how the data is displayed to you or unloaded to files through the value of session variables called \u003Ccode\u003EGEOMETRY_OUTPUT_FORMAT\u003C/code\u003E.\u003C/p\u003E\n","\u003Cp\u003ERun the query below to make sure the current format is GeoJSON:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EALTER SESSION SET geometry_output_format = 'GEOJSON';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThe \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/sql/alter-session.html\"\u003Ealter session\u003C/a\u003E command lets you set a parameter for your current user session, which in this case is  \u003Ccode\u003EGEOMETRY_OUTPUT_FORMAT\u003C/code\u003E. The default value for those parameters is \u003Ccode\u003E'GEOJSON'\u003C/code\u003E, so normally you wouldn't have to run this command if you want that format, but this guide wants to be certain the next queries are run with the \u003Ccode\u003E'GEOJSON'\u003C/code\u003E output.\u003C/p\u003E\n","\u003Cp\u003ENow run the following query against the \u003Ccode\u003Enl_cables_stations\u003C/code\u003E table to see energy grids in the Netherlands.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT geometry\nFROM nl_cables_stations\nLIMIT 5;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIn the result set, notice the \u003Ccode\u003EGEOMETRY\u003C/code\u003E column and how it displays a JSON representation of spatial objects. It should look similar to this:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003E{&quot;coordinates&quot;: [[[1.852040750000000e+05, 3.410349640000000e+05], \n[1.852044840000000e+05,3.410359860000000e+05]], \n[[1.852390240000000e+05,3.411219340000000e+05], \n... ,\n[1.852800600000000e+05,3.412219960000000e+05]]   ], \n&quot;type&quot;: &quot;MultiLineString&quot; }\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EUnlike \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E, which treats all points as longitude and latitude on a spherical earth, \u003Ccode\u003EGEOMETRY\u003C/code\u003E considers the Earth as a flat surface. More information about Snowflake's specification can be found \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/data-types-geospatial.html\"\u003Ehere\u003C/a\u003E.\nIn this example it uses scientific notation and the numbers are much larger than latitude and longitude boundaries [-180; 180].\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_57.png\" alt=\"assets/geo_ml_57.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ENow look at the same query but in a different format. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EALTER SESSION SET geometry_output_format = 'EWKT';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ERun the previous \u003Ccode\u003ESELECT\u003C/code\u003E query again and when done, examine the output in the \u003Ccode\u003EGEOMETRY\u003C/code\u003E column.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT geometry\nFROM nl_cables_stations\nLIMIT 5;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EEWKT looks different from GeoJSON, and is arguably more readable. Here you can more clearly see the \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/data-types-geospatial.html#geospatial-object-types\"\u003Egeospatial object types\u003C/a\u003E, which are represented below in the example output:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESRID=28992;MULTILINESTRING((185204.075 341034.964,185204.484 341035.986), ... ,(185276.402 341212.688,185279.319 341220.196,185280.06 341221.996))\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EEWKT also shows the spatial reference identifier and in our example, you have a dataset in \u003Ca href=\"https://epsg.io/28992\"\u003EAmersfoort / RD New\u003C/a\u003E spatial reference system, that is why the displayed SRID is 28992.\u003C/p\u003E\n","\u003Cp\u003ELastly, look at the WKB output. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EALTER SESSION SET geometry_output_format = 'WKB';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ERun the query again:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT geometry\nFROM nl_cables_stations\nLIMIT 5;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow that you have a basic understanding of how the \u003Ccode\u003EGEOMETRY\u003C/code\u003E data type works and what a geospatial representation of data looks like in various output formats, it's time to walk through a scenario that requires you to use constructors to load data.  You will do it while trying one more way of getting data, namely, from the Shapefile file stored in the external stage.\u003C/p\u003E\n","\u003Cp\u003EOne of the files in the external stage contains the polygons of administrative boundaries in the Netherlands. The data is stored in \u003Ca href=\"https://en.wikipedia.org/wiki/Shapefile\"\u003EShapefile format\u003C/a\u003E which is not yet supported by Snowflake. But you can load this file using Python UDF and \u003Ca href=\"https://docs.snowflake.com/developer-guide/udf/python/udf-python-examples#label-udf-python-read-files\"\u003EDynamic File Access feature\u003C/a\u003E. You will also use some packages available in the Snowflake Anaconda channel.\u003C/p\u003E\n","\u003Cp\u003ERun the following query that creates a UDF to read shapfiles:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE FUNCTION geolab.geometry.py_load_geodata(PATH_TO_FILE string, filename string)\nRETURNS TABLE (wkt varchar, properties object)\nLANGUAGE PYTHON\nRUNTIME_VERSION = 3.8\nPACKAGES = ('fiona', 'shapely', 'snowflake-snowpark-python')\nHANDLER = 'GeoFileReader'\nAS $$\nfrom shapely.geometry import shape\nfrom snowflake.snowpark.files import SnowflakeFile\nfrom fiona.io import ZipMemoryFile\nclass GeoFileReader:        \n    def process(self, PATH_TO_FILE: str, filename: str):\n    \twith SnowflakeFile.open(PATH_TO_FILE, 'rb') as f:\n    \t\twith ZipMemoryFile(f) as zip:\n    \t\t\twith zip.open(filename) as collection:\n    \t\t\t\tfor record in collection:\n    \t\t\t\t\tyield (shape(record['geometry']).wkt, dict(record['properties']))\n$$;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThis UDF reads a Shapefile and returns its content as a table. Under the hood it uses geospatial libraries \u003Ccode\u003Efiona\u003C/code\u003E and \u003Ccode\u003Eshapely\u003C/code\u003E.\nRun the following query to see the content of the uploaded shapefile.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EALTER SESSION SET geometry_output_format = 'EWKT';\n\nSELECT to_geometry(wkt) AS geometry,\n       properties:NAME_1::string AS province_name,\n       properties:NAME_2::string AS municipality_name\nFROM table(py_load_geodata(build_scoped_file_url(@geolab.geometry.geostage, 'nl_areas.zip'), 'nl_areas.shp'));\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThis query fails with the error \u003Cem\u003ENotebookSqlException: 100383: Geometry validation failed: Geometry has invalid self-intersections. A self-intersection point was found at (559963, 5.71069e+06)\u003C/em\u003E.\nThe constructor function determines if the shape is valid according to the \u003Ca href=\"https://www.ogc.org/standards/sfa\"\u003EOpen Geospatial Consortium&rsquo;s Simple Feature Access / Common Architecture\u003C/a\u003E standard. If the shape is invalid, the function reports an error and does not create the GEOMETRY object. That is what happened in our example.\u003C/p\u003E\n","\u003Cp\u003ETo fix this you can allow the ingestion of invalid shapes by setting the corresponding parameter to True. Let's run the SELECT statement again, but update the query to see how many shapes are invalid. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT to_geometry(s =&gt; wkt, allowInvalid =&gt; True) AS geometry,\n       st_isvalid(geometry) AS is_valid,\n       properties:NAME_1::string AS province_name,\n       properties:NAME_2::string AS municipality_name\nFROM table(py_load_geodata(build_scoped_file_url(@geolab.geometry.geostage, 'nl_areas.zip'), 'nl_areas.shp'))\nORDER BY is_valid ASC;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_50.png\" alt=\"assets/geo_ml_50.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EThis query completed without error and now you see that the shape of the province Zeeland is invalid. Let's repair it by applying the \u003Ca href=\"https://sedona.apache.org/1.5.1/api/snowflake/vector-data/Function/#st_makevalid\"\u003EST_MakeValid\u003C/a\u003E function from SedonaSnow Native app:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT SEDONASNOW.SEDONA.st_MakeValid(to_geometry(s =&gt; wkt, allowInvalid =&gt; True)) AS geometry,\n       st_isvalid(geometry) AS is_valid,\n       (CASE WHEN properties:TYPE_1::string IS NULL THEN 'Municipality' ELSE 'Province' END) AS type,\n       properties:NAME_1::string AS province_name,\n       properties:NAME_2::string AS municipality_name\nFROM table(py_load_geodata(build_scoped_file_url(@geolab.geometry.geostage, 'nl_areas.zip'), 'nl_areas.shp'))\nORDER BY is_valid ASC;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_51.png\" alt=\"assets/geo_ml_51.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ENow all shapes are valid and the data is ready to be ingested. One additional thing you should do is to set SRID, since otherwise it will be set to 0. This dataset is in the reference system \u003Ca href=\"https://epsg.io/32231\"\u003EWGS 72 / UTM zone 31N\u003C/a\u003E, so it makes sense to add the SRID=32231 to the constructor function.\u003C/p\u003E\n","\u003Cp\u003ERun the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE geolab.geometry.nl_administrative_areas AS\nSELECT ST_SETSRID(SEDONASNOW.SEDONA.ST_MakeValid(to_geometry(s =&gt; wkt, srid =&gt; 32231, allowInvalid =&gt; True)), 32231) AS geometry,\n       st_isvalid(geometry) AS is_valid,\n       (CASE WHEN properties:TYPE_1::string IS NULL THEN 'Municipality' ELSE 'Province' END) AS type,\n       properties:NAME_1::string AS province_name,\n       properties:NAME_2::string AS municipality_name\nFROM table(py_load_geodata(build_scoped_file_url(@geolab.geometry.geostage, 'nl_areas.zip'), 'nl_areas.shp'))\nORDER BY is_valid ASC;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EExcellent! Now that all the datasets are successfully loaded, let's proceed to the next exciting step: the analysis.\u003C/p\u003E\n","\u003Ch3\u003EEnergy grids Analysis\u003C/h3\u003E\n","\u003Cp\u003ETo showcase the capabilities of the GEOMETRY data type, you will explore several use cases. In these scenarios, you'll assume you are an analyst working for an energy utilities company responsible for maintaining electrical grids.\u003C/p\u003E\n","\u003Ch4\u003EWhat is the length of the electricity cables?\u003C/h4\u003E\n","\u003Cp\u003EIn the first use case you will calculate the length of electrical cables your organization is responsible for in each administrative area within the Netherlands. You'll be utilizing two datasets: with power infrastructure of the Netherlands and the borders of Dutch administrative areas. First, let's check the sample of each dataset.\u003C/p\u003E\n","\u003Cp\u003ERun the following query to see the content of \u003Ccode\u003Enl_cables_stations\u003C/code\u003E table:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT geometry, type\nFROM geolab.geometry.nl_cables_stations\nLIMIT 5;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_52.png\" alt=\"assets/geo_ml_52.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EThe spatial data is stored using the \u003Ccode\u003EGEOMETRY\u003C/code\u003E data type and employs the Dutch mapping system, \u003Ccode\u003EAmersfoort / RD New\u003C/code\u003E (SRID = 28992).\u003C/p\u003E\n","\u003Cp\u003ETo view the contents of the table containing the boundaries of the administrative areas in the Netherlands, execute the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT *\nFROM geolab.geometry.nl_administrative_areas\nLIMIT 5;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_53.png\" alt=\"assets/geo_ml_53.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EIn order to compute the length of all cables per administrative area, it's essential that both datasets adhere to the same mapping system. You have two options: either project \u003Ccode\u003Enl_administrative_areas\u003C/code\u003E to SRID 28992, or project \u003Ccode\u003Enl_cables_stations\u003C/code\u003E to SRID 32231. For this exercise, let's choose the first option. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT t1.province_name,\n       sum(st_length(t2.geometry)) AS cables_length\nFROM geolab.geometry.nl_administrative_areas AS t1,\n     geolab.geometry.nl_cables_stations AS t2\nWHERE st_intersects(st_transform(t1.geometry, 28992), t2.geometry)\n  AND t1.type = 'Province'\nGROUP BY 1\nORDER BY 2 DESC;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_54.png\" alt=\"assets/geo_ml_54.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EYou have five areas densely covered by electricity cables, those are the ones that your company is responsible for. For your first analysis, you will focus on these areas.\u003C/p\u003E\n","\u003Ch4\u003EWhat cell towers lack electricity cables nearby?\u003C/h4\u003E\n","\u003Cp\u003EIn many areas, especially rural or remote ones, cell towers might be located far from electricity grids. This can pose a challenge in providing a reliable power supply to these towers. They often rely on diesel generators, which can be expensive to operate and maintain and have environmental implications. Furthermore, power outages can lead to disruptions in mobile connectivity, impacting individuals, businesses, and emergency services.\u003C/p\u003E\n","\u003Cp\u003EOur analysis aims to identify mobile cell towers that are not near an existing electricity grid. This information could be used to prioritize areas for grid expansion, to improve the efficiency of renewable energy source installations (like solar panels or wind turbines), or to consider alternative energy solutions.\u003C/p\u003E\n","\u003Cp\u003EFor this and the next examples let's use \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E data type as it can be easily visualized using CARTO. As a first step, let's create \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E equivalents for the energy grids and boundaries tables. For that you need to project the \u003Ccode\u003Egeometry\u003C/code\u003E column in each of the tables into the mapping system WGS 84 (SRID=4326) and then convert to \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E data type. Run the following queries that create new tables and enable search optimization for each of them in order to increase the performance of spatial operations.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003E// Creating a table with GEOGRAPHY for nl_administrative_areas\nCREATE SCHEMA IF NOT EXISTS GEOLAB.GEOGRAPHY;\n\nCREATE OR REPLACE TABLE geolab.geography.nl_administrative_areas AS\nSELECT to_geography(st_transform(geometry, 4326)) AS geom,\n       type,\n       province_name,\n       municipality_name\nFROM geolab.geometry.nl_administrative_areas\nORDER BY st_geohash(geom);\n\n// Creating a table with GEOGRAPHY for nl_cables_stations\nCREATE OR REPLACE TABLE geolab.geography.nl_cables_stations AS\nSELECT to_geography(st_transform(geometry, 4326)) AS geom,\n       id,\n       type\nFROM geolab.geometry.nl_cables_stations\nORDER BY st_geohash(geom);\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you will create a table with locations of cell towers stored as GEOGRAPHY, just like for the previous two tables. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE geolab.geography.nl_lte AS\nSELECT DISTINCT st_point(lon, lat) AS geom,\n                cell_range\nFROM OPENCELLID.PUBLIC.RAW_CELL_TOWERS t1\nWHERE mcc = '204' -- 204 is the mobile country code in the Netherlands\nAND radio='LTE'\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EFinally, you will find all cell towers that don't have an energy line within a 2-kilometer radius. For each cell tower you'll calculate the distance to the nearest electricity cable. You will use Streamlit library \u003Ccode\u003Epydeck\u003C/code\u003E to visualise municipalities and locations of cell towers.\u003C/p\u003E\n","\u003Cp\u003EYou can create visualisation either in Notebooks or as a Strealit app. As a preparation step you need to import pydeck library that you will use in this Lab. Navigate to the \u003Ccode\u003EPackages\u003C/code\u003E drop-down  in the upper right of the Notebook (upper left of the Streamlit app) and search for \u003Ccode\u003Epydeck\u003C/code\u003E. Click on \u003Ccode\u003Epydeck\u003C/code\u003E to add it to the Python packages. Then run the following Python code:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Eimport streamlit as st\nimport pandas as pd\nimport pydeck as pdk\nimport json\nfrom snowflake.snowpark.context import get_active_session\n\nsession = get_active_session()\n\ndef get_celltowers() -&gt; pd.DataFrame:\n    return session.sql(f&quot;&quot;&quot;\n    SELECT province_name,\n    cells.geom\n    FROM geolab.geography.nl_lte cells\n    LEFT JOIN geolab.geography.nl_cables_stations cables\n    ON st_dwithin(cells.geom, cables.geom, 2000)\n    JOIN geolab.geography.nl_administrative_areas areas \n    ON st_contains(areas.geom, cells.geom)\n    WHERE areas.type = 'Municipality'\n    AND areas.province_name in ('Noord-Brabant', 'Overijssel', 'Limburg', 'Groningen', 'Drenthe')\n    AND cables.geom IS NULL; &quot;&quot;&quot;).to_pandas()\n\ndef get_boundaries() -&gt; pd.DataFrame:\n    return session.sql(f&quot;&quot;&quot;\n        SELECT st_simplify(GEOM, 10) as geom, municipality_name\n        FROM geolab.geography.nl_administrative_areas\n        WHERE type = 'Municipality';\n    &quot;&quot;&quot;).to_pandas()\n\n\nboundaries = get_boundaries()\nboundaries[&quot;coordinates&quot;] = boundaries[&quot;GEOM&quot;].apply(lambda row: json.loads(row)[&quot;coordinates&quot;][0])\n\ncelltowers = get_celltowers()\ncelltowers[&quot;lon&quot;] = celltowers[&quot;GEOM&quot;].apply(lambda row: json.loads(row)[&quot;coordinates&quot;][0])\ncelltowers[&quot;lat&quot;] = celltowers[&quot;GEOM&quot;].apply(lambda row: json.loads(row)[&quot;coordinates&quot;][1])\n\nlayer_celltowers = pdk.Layer(\n            &quot;ScatterplotLayer&quot;,\n            celltowers,\n            get_position=[&quot;lon&quot;, &quot;lat&quot;],\n            id=&quot;celltowers&quot;,\n            stroked=True,\n            filled=True,\n            extruded=False,\n            wireframe=True,\n            get_fill_color=[233, 43, 65],\n            get_line_color=[233, 43, 65],\n            get_radius=300,\n            auto_highlight=True,\n            pickable=False,\n        )\n\nlayer_boundaries = pdk.Layer(\n    &quot;PolygonLayer&quot;,\n    data=boundaries,\n    id=&quot;province-layer&quot;,\n    get_polygon=&quot;coordinates&quot;,\n    extruded=False,\n    opacity=0.9,\n    wireframe=True,\n    pickable=True,\n    stroked=True,\n    filled=True,\n    line_width_min_pixels=1,\n    get_line_color=[17, 86, 127],       # Red color for the border\n    get_fill_color=[43, 181, 233, 30],  # Blue fill with transparency\n    coverage=1\n)\n\n\nst.pydeck_chart(pdk.Deck(\n    map_style=None,\n    initial_view_state=pdk.ViewState(\n        latitude=51.97954426323304,\n        longitude=5.626041932127842, \n        # pitch=45, \n        zoom=8),\n    tooltip={\n            'html': '&lt;b&gt;Province name:&lt;/b&gt; {MUNICIPALITY_NAME}',\n             'style': {\n                 'color': 'white'\n                 }\n            },\n    layers=[layer_boundaries, layer_celltowers],\n))\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_56.png\" alt=\"assets/geo_ml_56.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EAnother way to visualise geospatial data is using open-source geo analytics tool QGIS. Do the following steps:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EInstall the latest \u003Ca href=\"https://qgis.org/download/\"\u003ELong Term Version of QGIS\u003C/a\u003E\u003C/li\u003E\u003Cli\u003EInstall Snowflake conector. Go to \u003Ccode\u003EPlugins\u003C/code\u003E &gt; \u003Ccode\u003EAll\u003C/code\u003E, search for \u003Ccode\u003ESnowflake Connector for QGIS\u003C/code\u003E and click \u003Ccode\u003EInstall Plugin\u003C/code\u003E.\u003C/li\u003E\u003Cli\u003EGo to \u003Ccode\u003ELayer\u003C/code\u003E &gt; \u003Ccode\u003EData Source Manager\u003C/code\u003E and create a new connection to Snowflake. Call it \u003Ccode\u003ESNOWFLAKE\u003C/code\u003E (all letters capital). \u003Ca href=\"https://github.com/snowflakedb/qgis-snowflake-connector?tab=readme-ov-file#getting-started\"\u003ECheck\u003C/a\u003E the documentation to learn mor on how to create new coonection\u003C/li\u003E\u003Cli\u003E\u003Ca href=\"https://sfquickstarts.s3.us-west-1.amazonaws.com/vhol_spatial_analysis_geometry_geography/energy_grids_nl.qgz\"\u003EDownload\u003C/a\u003E a QGIS project that we created for you and open it in QGIS.\u003C/li\u003E\u003Cli\u003EIf previous steps done correctly, you should be able to see the following layers in QGIS\n\u003Cul\u003E\u003Cli\u003E\u003Ccode\u003EENERGY_GRIDS\u003C/code\u003E (LINESTRING and MULTILINESTRING) - energy frids for Noord-Brabant, Overijssel, Limburg, Groningen, and Drenthe.\u003C/li\u003E\u003Cli\u003E\u003Ccode\u003ECELL_TOWERS_WITHOUT_CABLES\u003C/code\u003E - cell towers in the regions above that don't have energy grids within radius of 2km.\u003C/li\u003E\u003Cli\u003E\u003Ccode\u003EMunicipalities\u003C/code\u003E (POLYGON and MULTIPOLYGON) - Boundaries of Dutch municipalities.\u003C/li\u003E\u003C/ul\u003E\n\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003EConclusion\u003C/h3\u003E\n","\u003Cp\u003EIn this guide, you acquired geospatial data from the Snowflake Marketplace, explored how the \u003Ccode\u003EGEOMETRY\u003C/code\u003E data type works and how it differs from \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E. You converted one data type into another and queried geospatial data using parser, constructor, transformation, and used geospatial joins. You then saw how geospatial objects could be visualized using CARTO.\u003C/p\u003E\n","\u003Cp\u003EYou are now ready to explore the larger world of Snowflake geospatial support and geospatial functions.\u003C/p\u003E\n","\u003Ch3\u003EWhat we've covered\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003EHow to acquire a shared database from the Snowflake Marketplace and from External and internal storages.\u003C/li\u003E\u003Cli\u003EThe GEOMETRY data type, its formats GeoJSON, WKT, EWKT, WKB, and EWKB, and how to switch between them.\u003C/li\u003E\u003Cli\u003EHow to use constructors like TO_GEOMETRY, ST_MAKELINE.\u003C/li\u003E\u003Cli\u003EHow to reproject between SRIDs using ST_TRANSFORM.\u003C/li\u003E\u003Cli\u003EHow to perform relational calculations like ST_DWITHIN and ST_INTERSECTS.\u003C/li\u003E\u003Cli\u003EHow to perform measurement calculations like ST_LENGTH.\u003C/li\u003E\u003Cli\u003EHow to use Python UDFs for reading Shapefiles and creating custom functions.\u003C/li\u003E\u003Cli\u003EHow to visualise geospatial data using Streamlit and QGIS\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch2\u003EGeocoding and Reverse Geocoding\u003C/h2\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EBefore starting with this lab, complete the preparation steps from \u003Ccode\u003ESetup your account\u003C/code\u003E page.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EThis lab is \u003Ca href=\"https://github.com/Snowflake-Labs/sf-guide-geospatial-analytics-ai-ml\"\u003Eavailable\u003C/a\u003E as Snowflake Notebook.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003EIn this lab, we will demonstrate how to perform geocoding and reverse geocoding using datasets and applications from the Marketplace. You will learn how to:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EPerform address cleansing\u003C/li\u003E\u003Cli\u003EConvert an address into a location (geocoding)\u003C/li\u003E\u003Cli\u003EConvert a location into an address (reverse geocoding)\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EFor the most precise and reliable geocoding results, we recommend using specialized services like \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0ZIFQPEA/mapbox-mapbox-geocoding-analysis-tools\"\u003EMapbox\u003C/a\u003E or \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZ2FSZKSSH1/traveltime-technologies-ltd-traveltime\"\u003ETravelTime\u003C/a\u003E. While the methods described in this Lab can be useful, they may not always achieve the highest accuracy, especially in areas with sparse data or complex geographic features. If your application demands extremely precise geocoding, consider investing in a proven solution with guaranteed accuracy and robust support.\u003C/p\u003E\n","\u003Cp\u003EHowever, many companies seek cost-effective solutions for geocoding large datasets. In such cases, supplementing specialized services with free datasets can be a viable approach. Datasets provided by the \u003Ca href=\"https://overturemaps.org/\"\u003EOverture Maps Foundation\u003C/a\u003E or \u003Ca href=\"https://openaddresses.io/\"\u003EOpenAddresses\u003C/a\u003E can be valuable resources for building solutions that are &quot;good enough&quot;, especially when some accuracy can be compromised in favor of cost-efficiency. It's essential to evaluate your specific needs and constraints before selecting a geocoding approach.\u003C/p\u003E\n","\u003Ch3\u003EStep 1. Data acquisition\u003C/h3\u003E\n","\u003Cp\u003EFor this project you will use a dataset with locations of restaurants and cafes in Berlin from the \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9J2/carto-carto-academy-data-for-tutorials\"\u003ECARTO Academy\u003C/a\u003E Marketplace listing.\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ENavigate to the \u003Ccode\u003EMarketplace\u003C/code\u003E screen using the menu on the left side of the window\u003C/li\u003E\u003Cli\u003ESearch for \u003Ccode\u003ECARTO Academy\u003C/code\u003E in the search bar\u003C/li\u003E\u003Cli\u003EFind and click the \u003Ccode\u003ECARTO Academy - Data for tutorials\u003C/code\u003E tile\u003C/li\u003E\u003Cli\u003EOnce in the listing, click the big blue \u003Ccode\u003EGet\u003C/code\u003E button\u003C/li\u003E\u003C/ul\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EOn the \u003Ccode\u003EGet\u003C/code\u003E screen, you may be prompted to complete your \u003Ccode\u003Euser profile\u003C/code\u003E if you have not done so before. Click the link as shown in the screenshot below. Enter your name and email address into the profile screen and click the blue \u003Ccode\u003ESave\u003C/code\u003E button. You will be returned to the \u003Ccode\u003EGet\u003C/code\u003E screen.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_10.png\" alt=\"assets/geo_ml_10.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EAnother dataset that you will use in this Lab is \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZSNZ7F5UT/starschema-worldwide-address-data\"\u003EWorldwide Address Data\u003C/a\u003E and you can also get it from the Snowflake Marketplace. It's a free dataset from the OpenAddresses project that allows Snowflake customers to map lat/long information to address details.\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ESearch for \u003Ccode\u003EWorldwide Address Data\u003C/code\u003E in the search bar\u003C/li\u003E\u003Cli\u003EFind and click on the corresponding dataset from Starschema\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_20.png\" alt=\"assets/geo_ml_20.png\"\u003E\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EOn the \u003Ccode\u003EGet Data\u003C/code\u003E screen, don't change the name of the database from \u003Ccode\u003EWORLDWIDE_ADDRESS_DATA\u003C/code\u003E.\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_21.png\" alt=\"assets/geo_ml_21.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ENice! You have just got two listings that you will need for this project.\u003C/p\u003E\n","\u003Ch3\u003EStep 2. Data Preparation\u003C/h3\u003E\n","\u003Cp\u003ETo showcase geocoding techniques in this lab, and to evaluate the quality of our approach you will use a table \u003Ccode\u003ECARTO_ACADEMY__DATA_FOR_TUTORIALS.CARTO.DATAAPPEAL_RESTAURANTS_AND_CAFES_BERLIN_CPG\u003C/code\u003E with locations of restaurants and cafes in Berlin. If you look into that table you will notice that some records don't have full or correct information in the \u003Ccode\u003ESTREET_ADDRESS\u003C/code\u003E column. To be able to calculate the correct quality metrics in this lab lets do a simple cleanup of the low quality datapoint. Run the following query to create a table that has only records that have 5-digits postcode and those records are in Berlin.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.GEOCODING_ADDRESSES AS\nSELECT * \nFROM CARTO_ACADEMY__DATA_FOR_TUTORIALS.CARTO.DATAAPPEAL_RESTAURANTS_AND_CAFES_BERLIN_CPG\nWHERE REGEXP_SUBSTR(street_address, '(\\\\d{5})') is not null\nAND city ILIKE 'berlin';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIf you check the size of \u003Ccode\u003EADVANCED_ANALYTICS.PUBLIC.GEOCODING_ADDRESSES\u003C/code\u003E table you'll see that it has about 10K rows.\u003C/p\u003E\n","\u003Cp\u003EThe Worldwide Address Data dataset contains more than 500M addresses around the world and we will use it for geocoding and reverse geocoding. However some addresses in that dataset contain addresses with coordinates outside of the allowed boundaries for latitude and longitude. Run the following query to create a new table that filters out those &quot;invalid&quot; records and includes a new column, \u003Ccode\u003ELOCATION\u003C/code\u003E, which stores the locations in the \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E type:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.OPENADDRESS AS\nSELECT ST_POINT(lon, lat) as location, *\nFROM WORLDWIDE_ADDRESS_DATA.ADDRESS.OPENADDRESS\nWHERE lon between -180 and 180\nAND lat between -90 and 90;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow when all your data is ready and clean, you can proceed to the actual use cases.\u003C/p\u003E\n","\u003Ch3\u003EStep 2. Data Cleansing\u003C/h3\u003E\n","\u003Cp\u003ECustomer-provided address data is often incomplete or contains spelling mistakes. If you plan to perform geocoding on that data, it would be a good idea to include address cleansing as a preparation step.\u003C/p\u003E\n","\u003Cp\u003EIn this step, you will prepare a prompt to run the data cleansing. For this task, you will use the \u003Ca href=\"https://docs.snowflake.com/en/sql-reference/functions/complete-snowflake-cortex\"\u003ECORTEX.COMPLETE()\u003C/a\u003E function because it is purpose-built for data processing and data generation tasks. First, let's create a Cortex role. In the query below, replace AA with the username you used to log in to Snowflake.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE ROLE IF NOT EXISTS cortex_user_role;\nGRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE cortex_user_role;\n\nGRANT ROLE cortex_user_role TO USER AA;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EYou are now ready to provide the CORTEX.COMPLETE() function with instructions on how to perform address cleansing. Specifically, using a table of Berlin restaurants, you'll create a new table with an additional column \u003Ccode\u003Eparsed_address\u003C/code\u003E, which is the result of the \u003Ccode\u003ECORTEX.COMPLETE()\u003C/code\u003E function. For complex processing like this, you will use \u003Ca href=\"https://docs.snowflake.com/en/user-guide/snowflake-cortex/llm-functions#availability\"\u003Emistral-8x7b\u003C/a\u003E, a very capable open-source LLM created by Mistral AI. Essentially, we want to parse the address stored as a single string into a JSON object that contains each component of the address as a separate key.\u003C/p\u003E\n","\u003Cp\u003EAs a general rule when writing a prompt, the instructions should be simple, clear, and complete. For example, you should clearly define the task as parsing an address into a JSON object. It's important to define the constraints of the desired output; otherwise, the LLM may produce unexpected results. Below, you specifically instruct the LLM to parse the address stored as text and explicitly tell it to respond in JSON format.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES as\nSELECT geom, geoid, street_address, name,\n    snowflake.cortex.complete('mixtral-8x7b', \n    concat('Task: Your job is to return a JSON formatted response that normalizes, standardizes, and enriches the following address,\n            filling in any missing information when needed: ', street_address, \n            'Requirements: Return only in valid JSON format (starting with { and ending with }).\n            The JSON response should include the following fields:\n            &quot;number&quot;: &lt;&lt;house_number&gt;&gt;,\n            &quot;street&quot;: &lt;&lt;street_name&gt;&gt;,\n            &quot;city&quot;: &lt;&lt;city_name&gt;&gt;,\n            &quot;postcode&quot;: &lt;&lt;postcode_value&gt;&gt;,\n            &quot;country&quot;: &lt;&lt;ISO_3166-1_alpha-2_country_code&gt;&gt;.\n            Values inside &lt;&lt;&gt;&gt; must be replaced with the corresponding details from the address provided.\n            - If a value cannot be determined, use &quot;Null&quot;.\n            - No additional fields or classifications should be included beyond the five categories listed.\n            - Country code must follow the ISO 3166-1 alpha-2 standard.\n            - Do not add comments or any other non-JSON text.\n            - Use Latin characters for street names and cities, avoiding Unicode alternatives.\n            Examples:\n            Input: &quot;123 Mn Stret, San Franscico, CA&quot;\n            Output: {&quot;number&quot;: &quot;123&quot;, &quot;street&quot;: &quot;Main Street&quot;, &quot;city&quot;: &quot;San Francisco&quot;, &quot;postcode&quot;: &quot;94105&quot;, &quot;country&quot;: &quot;US&quot;}\n            Input: &quot;45d Park Avnue, New Yrok, NY 10016&quot;\n            Output: {&quot;number&quot;: &quot;45d&quot;, &quot;street&quot;: &quot;Park Avenue&quot;, &quot;city&quot;: &quot;New York&quot;, &quot;postcode&quot;: &quot;10016&quot;, &quot;country&quot;: &quot;US&quot;}\n            Input: &quot;10 Downig Stret, Londn, SW1A 2AA, United Knidom&quot;\n            Output: {&quot;number&quot;: &quot;10&quot;, &quot;street&quot;: &quot;Downing Street&quot;, &quot;city&quot;: &quot;London&quot;, &quot;postcode&quot;: &quot;SW1A 2AA&quot;, &quot;country&quot;: &quot;UK&quot;}\n            Input: &quot;4 Avneu des Champs Elyses, Paris, France&quot;\n            Output: {&quot;number&quot;: &quot;4&quot;, &quot;street&quot;: &quot;Avenue des Champs-&Eacute;lys&eacute;es&quot;, &quot;city&quot;: &quot;Paris&quot;, &quot;postcode&quot;: &quot;75008&quot;, &quot;country&quot;: &quot;FR&quot;}\n            Input: &quot;1600 Amiphiteatro Parkway, Montain View, CA 94043, USA&quot;\n            Output: {&quot;number&quot;: &quot;1600&quot;, &quot;street&quot;: &quot;Amphitheatre Parkway&quot;, &quot;city&quot;: &quot;Mountain View&quot;, &quot;postcode&quot;: &quot;94043&quot;, &quot;country&quot;: &quot;US&quot;}\n            Input: &quot;Plaza de Espana, 28c, Madird, Spain&quot;\n            Output: {&quot;number&quot;: &quot;28c&quot;, &quot;street&quot;: &quot;Plaza de Espa&ntilde;a&quot;, &quot;city&quot;: &quot;Madrid&quot;, &quot;postcode&quot;: &quot;28008&quot;, &quot;country&quot;: &quot;ES&quot;}\n            Input: &quot;1d Prinzessinenstrase, Berl&iacute;n, 10969, Germany&quot;\n            Output: {&quot;number&quot;: &quot;1d&quot;, &quot;street&quot;: &quot;Prinzessinnenstra&szlig;e&quot;, &quot;city&quot;: &quot;Berlin&quot;, &quot;postcode&quot;: &quot;10969&quot;, &quot;country&quot;: &quot;DE&quot;} ')) as parsed_address \n        FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODING_ADDRESSES;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EOn a \u003Ccode\u003ELARGE\u003C/code\u003E warehouse, which we used in this quickstart, the query completed in about 13 minutes. However, on a smaller warehouse, the completion time is roughly the same. We don't recommend using a warehouse larger than \u003Ccode\u003EMEDIUM\u003C/code\u003E for CORTEX LLM functions, as it won't significantly reduce execution time. If you plan to execute complex processing with LLM on a large dataset, it's better to split the dataset into chunks up to 100K rows each and run multiple jobs in parallel using an \u003Ccode\u003EX-Small\u003C/code\u003E warehouse. A rule of thumb is that on an \u003Ccode\u003EX-Small\u003C/code\u003E, data cleansing of 1,000 rows can be done within 90 seconds, which costs about 5 cents.\u003C/p\u003E\n","\u003Cp\u003ENow, you will convert the parsed address into JSON type:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES AS\nSELECT geoid, geom, street_address, name,\nTRY_PARSE_JSON(parsed_address) AS parsed_address,\nFROM ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ERun the following query to check what the result of cleansing looks like in the \u003Ccode\u003EPARSED_ADDRESS\u003C/code\u003E column and compare it with the actual address in the \u003Ccode\u003ESTREET_ADDRESS\u003C/code\u003E column.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EALTER SESSION SET GEOGRAPHY_OUTPUT_FORMAT='WKT';\n\nSELECT TOP 10 * FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EYou also can notice that 23 addresses were not correctly parsed, but if you look into the \u003Ccode\u003ESTREET_ADDRESS\u003C/code\u003E column of those records using the following query, you can understand why they were not parsed: in most cases there are some address elements missing in the initial address.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT * FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES\nWHERE parsed_address IS NULL;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003EStep3. Geocoding\u003C/h3\u003E\n","\u003Cp\u003EIn this step, you will use the Worldwide Address Data to perform geocoding. You will join this dataset with your cleansed address data using country, city, postal code, street, and building number as keys. For street name comparison, you will use \u003Ca href=\"https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance\"\u003EJaro-Winkler distance\u003C/a\u003E to measure similarity between the two strings. You should use a sufficiently high similarity threshold but not 100%, which would imply exact matches. Approximate similarity is necessary to account for potential variations in street names, such as &quot;Street&quot; versus &quot;Stra&szlig;e&quot;.\u003C/p\u003E\n","\u003Cp\u003ETo the initial table with actual location and address, you will add columns with geocoded and parsed values for country, city, postcode, street, and building number. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.GEOCODED AS\nSELECT \n    t1.name,\n    t1.geom AS actual_location,\n    t2.location AS geocoded_location, \n    t1.street_address as actual_address,\n    t2.street as geocoded_street, \n    t2.postcode as geocoded_postcode, \n    t2.number as geocoded_number, \n    t2.city as geocoded_city\nFROM ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES t1\nLEFT JOIN ADVANCED_ANALYTICS.PUBLIC.OPENADDRESS t2\nON t1.parsed_address:postcode::string = t2.postcode\nAND t1.parsed_address:number::string = t2.number\nAND LOWER(t1.parsed_address:country::string) = LOWER(t2.country)\nAND LOWER(t1.parsed_address:city::string) = LOWER(t2.city)\nAND JAROWINKLER_SIMILARITY(LOWER(t1.parsed_address:street::string), LOWER(t2.street)) &gt; 95;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow let's analyze the results of geocoding and compare the locations we obtained after geocoding with the original addresses. First, let's see how many addresses we were not able to geocode using this approach.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT count(*) FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODED\nWHERE geocoded_location IS NULL;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIt turned out that 2,081 addresses were not geocoded, which is around 21% of the whole dataset. Let's see how many geocoded addresses deviate from the original location by more than 200 meters.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT COUNT(*) FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODED\nWHERE ST_DISTANCE(actual_location, geocoded_location) &gt; 200;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIt seems there are 174 addresses. Let's examine random records from these 174 addresses individually by running the query below. You can visualize coordinates from the table with results using \u003Ca href=\"https://clydedacruz.github.io/openstreetmap-wkt-playground/\"\u003Ethis\u003C/a\u003E service (copy-paste \u003Ccode\u003EGEOCODED_LOCATION\u003C/code\u003E and \u003Ccode\u003EACTUAL_LOCATION\u003C/code\u003E values).\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT * FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODED\nWHERE ST_DISTANCE(actual_location, geocoded_location) &gt; 200;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EYou can see that in many cases, our geocoding provided the correct location for the given address, while the original location point actually corresponds to a different address. Therefore, our approach returned more accurate locations than those in the original dataset. Sometimes, the &quot;ground truth&quot; data contains incorrect data points.\u003C/p\u003E\n","\u003Cp\u003EIn this exercise, you successfully geocoded more than 78% of the entire dataset. To geocode the remaining addresses that were not geocoded using this approach, you can use paid services such as \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0ZIFQPEA/mapbox-mapbox-geocoding-analysis-tools\"\u003EMapbox\u003C/a\u003E or \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZ2FSZKSSH1/traveltime-technologies-ltd-traveltime\"\u003ETravelTime\u003C/a\u003E. However, you managed to reduce the geocoding cost by more than four times compared to what it would have been if you had used those services for the entire dataset.\u003C/p\u003E\n","\u003Ch3\u003EStep 4. Reverse Geocoding\u003C/h3\u003E\n","\u003Cp\u003EIn the next step, we will do the opposite - for a given location, we will get the address. Often, companies have location information and need to convert it into the actual address. Similar to the previous example, the best way to do reverse geocoding is to use specialized services, such as Mapbox or TravelTime. However, there are cases where you're ready to trade off between accuracy and cost. For example, if you don't need an exact address but a zip code would be good enough. In this case, you can use free datasets to perform reverse geocoding.\u003C/p\u003E\n","\u003Cp\u003ETo complete this exercise, we will use the nearest neighbor approach. For locations in our test dataset (\u003Ccode\u003EADVANCED_ANALYTICS.PUBLIC.GEOCODING_ADDRESSES\u003C/code\u003E table), you will find the closest locations from the Worldwide Address Data. Let's first create a procedure that, for each row in the given table with addresses, finds the closest address from the Worldwide Address Data table within the radius of 5km. To speed up the function we will apply an iterative approach to the neighbor search - start from 10 meters and increase the search radius until a match is found or the maximum radius is reached. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE PROCEDURE GEOCODING_EXACT(\n    NAME_FOR_RESULT_TABLE TEXT,\n    LOCATIONS_TABLE_NAME TEXT,\n    LOCATIONS_ID_COLUMN_NAME TEXT,\n    LOCATIONS_COLUMN_NAME TEXT,\n    WWAD_TABLE_NAME TEXT,\n    WWAD_COLUMN_NAME TEXT\n)\nRETURNS TEXT\nLANGUAGE SQL\nAS $$\nDECLARE\n    -- Initialize the search radius to 10 meters.\n    RADIUS REAL DEFAULT 10.0;\nBEGIN\n    -- **********************************************************************\n    -- Procedure: GEOCODING_EXACT\n    -- Description: This procedure finds the closest point from the Worldwide \n    --              Address Data table for each location in the LOCATIONS_TABLE. \n    --              It iteratively increases the search radius until a match is \n    --              found or the maximum radius is reached.\n    -- **********************************************************************\n\n    -- Create or replace the result table with the required schema but no data.\n    EXECUTE IMMEDIATE '\n        CREATE OR REPLACE TABLE ' || NAME_FOR_RESULT_TABLE || ' AS\n        SELECT\n            ' || LOCATIONS_ID_COLUMN_NAME || ',\n            ' || LOCATIONS_COLUMN_NAME || ' AS LOCATION_POINT,\n            ' || WWAD_COLUMN_NAME || ' AS CLOSEST_LOCATION_POINT,\n            t2.NUMBER,\n            t2.STREET,\n            t2.UNIT,\n            t2.CITY,\n            t2.DISTRICT,\n            t2.REGION,\n            t2.POSTCODE,\n            t2.COUNTRY,\n            0.0::REAL AS DISTANCE\n        FROM\n            ' || LOCATIONS_TABLE_NAME || ' t1,\n            ' || WWAD_TABLE_NAME || ' t2\n        LIMIT 0';\n\n-- Define a sub-query to select locations not yet processed.\n    LET REMAINING_QUERY := '\n        SELECT\n            ' || LOCATIONS_ID_COLUMN_NAME || ',\n            ' || LOCATIONS_COLUMN_NAME || '\n        FROM\n            ' || LOCATIONS_TABLE_NAME || '\n        WHERE\n            NOT EXISTS (\n                SELECT 1\n                FROM ' || NAME_FOR_RESULT_TABLE || ' tmp\n                WHERE ' || LOCATIONS_TABLE_NAME || '.' || LOCATIONS_ID_COLUMN_NAME || ' = tmp.' || LOCATIONS_ID_COLUMN_NAME || '\n            )';\n\n-- Iteratively search for the closest point within increasing radius.\n    FOR I IN 1 TO 10 DO\n-- Insert closest points into the result table for \n-- locations within the current radius.\n        EXECUTE IMMEDIATE '\n            INSERT INTO ' || NAME_FOR_RESULT_TABLE || '\n            WITH REMAINING AS (' || :REMAINING_QUERY || ')\n            SELECT\n                ' || LOCATIONS_ID_COLUMN_NAME || ',\n                ' || LOCATIONS_COLUMN_NAME || ' AS LOCATION_POINT,\n                points.' || WWAD_COLUMN_NAME || ' AS CLOSEST_LOCATION_POINT,\n                points.NUMBER,\n                points.STREET,\n                points.UNIT,\n                points.CITY,\n                points.DISTRICT,\n                points.REGION,\n                points.POSTCODE,\n                points.COUNTRY,\n                ST_DISTANCE(' || LOCATIONS_COLUMN_NAME || ', points.' || WWAD_COLUMN_NAME || ') AS DISTANCE\n            FROM\n                REMAINING\n            JOIN\n                ' || WWAD_TABLE_NAME || ' points\n            ON\n                ST_DWITHIN(\n                    REMAINING.' || LOCATIONS_COLUMN_NAME || ',\n                    points.' || WWAD_COLUMN_NAME || ',\n                    ' || RADIUS || '\n                )\n            QUALIFY\n                ROW_NUMBER() OVER (\n                    PARTITION BY ' || LOCATIONS_ID_COLUMN_NAME || '\n                    ORDER BY DISTANCE\n                ) &lt;= 1';\n\n        -- Double the radius for the next iteration.\n        RADIUS := RADIUS * 2;\n    END FOR;\nEND\n$$;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ERun the next query to call that procedure and store results of reverse geocoding to \u003Ccode\u003EADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED\u003C/code\u003E table:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECALL GEOCODING_EXACT('ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED', 'ADVANCED_ANALYTICS.PUBLIC.GEOCODING_ADDRESSES', 'GEOID', 'GEOM', 'ADVANCED_ANALYTICS.PUBLIC.OPENADDRESS', 'LOCATION');\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThis query completed in 5.5 minutes on \u003Ccode\u003ELARGE\u003C/code\u003E warehouse, which corresponds to about 2 USD. Let's now compare the address we get after the reverse geocoding (\u003Ccode\u003EADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED\u003C/code\u003E table) with the table that has the original address.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT t1.geoid, \n    t2.street_address AS actual_address,\n    t1.street || ' ' || t1.number || ', ' || t1.postcode || ' ' || t1.city  || ', ' || t1.country AS geocoded_address\nFROM ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED t1\nINNER JOIN ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES t2\n    ON t1.geoid = t2.geoid\nWHERE t1.distance &lt; 100;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EFor 9830 records, the closest addresses we found are within 100 meters from the original address. This corresponds to 98.7% of cases. As we mentioned earlier, often for analysis you might not need the full address, and knowing a postcode is already good enough. Run the following query to see for how many records the geocoded postcode is the same as the original postcode:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT count(*)\nFROM ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED t1\nINNER JOIN ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES t2\n    ON t1.geoid = t2.geoid\nWHERE t2.parsed_address:postcode::string = t1.postcode::string;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThis query returned 9564 records,  about 96% of the dataset, which is quite a good result.\u003C/p\u003E\n","\u003Cp\u003EOut of curiosity, let's see, for how many addresses the geocoded and initial address is the same up until the street name. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT count(*)\nFROM ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED t1\nINNER JOIN ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES t2\n    ON t1.geoid = t2.geoid\nWHERE t2.parsed_address:postcode::string = t1.postcode\nAND LOWER(t2.parsed_address:country::string) = LOWER(t1.country)\nAND LOWER(t2.parsed_address:city::string) = LOWER(t1.city)\nAND JAROWINKLER_SIMILARITY(LOWER(t2.parsed_address:street::string), LOWER(t1.street)) &gt; 95;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E82% of addresses correctly geocoded up to the street name. And to have a full picture, let's see how many records have the fully identical original and geocoded address:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT count(*)\nFROM ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED t1\nINNER JOIN ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES t2\n    ON t1.geoid = t2.geoid\nWHERE t2.parsed_address:postcode::string = t1.postcode\nAND t2.parsed_address:number::string = t1.number\nAND LOWER(t2.parsed_address:country::string) = LOWER(t1.country)\nAND LOWER(t2.parsed_address:city::string) = LOWER(t1.city)\nAND JAROWINKLER_SIMILARITY(LOWER(t2.parsed_address:street::string), LOWER(t1.street)) &gt; 95;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EFor 61% of addresses we were able to do reverse geocoding that matches reference dataset up to the rooftop.\u003C/p\u003E\n","\u003Ch3\u003EConclusion\u003C/h3\u003E\n","\u003Cp\u003EIn this lab, you have learned how to perform geocoding and reverse geocoding using free datasets and open-source tools. While this approach may not provide the highest possible accuracy, it offers a cost-effective solution for processing large datasets where some degree of inaccuracy is acceptable. It's important to mention that Worldwide Address Data that has more than 500M addresses  for the whole world is one of many free datasets that you can get from Snowflake Marketplace and use for geocoding use cases. There are others, which you might consider for your use cases, here are just some examples:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003E\u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9NQ/carto-overture-maps-addresses\"\u003EOverture Maps - Addresses\u003C/a\u003E - if you mainly need to geocode addresses in North America, another good option would be to use this dataset that has more than 200M addresses.\u003C/li\u003E\u003Cli\u003E\u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZTSZ290BV255\"\u003ESnowflake Public Data\u003C/a\u003E - includes a Global Government, Demographics &amp; Geospatial database, with US addresses and POIs.\u003C/li\u003E\u003Cli\u003E\u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT1ZQXT8U/atos-french-national-addresses\"\u003EFrench National Addresses\u003C/a\u003E - contains about 26M addresses in France.\u003C/li\u003E\u003Cli\u003E\u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZ1M7Z62O2A/tensing-dutch-addresses-buildings-registration-bag\"\u003EDutch Addresses &amp; Buildings Registration (BAG)\u003C/a\u003E - includes Dutch Addresses.\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EThere is a high chance that datasets focused on particular counties have richer and more accurate data for those countries. And by amending queries from this lab you can find the best option for your needs.\u003C/p\u003E\n","\u003Ch2\u003EForecasting time series on a map\u003C/h2\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EBefore starting with this lab, complete preparation steps from \u003Ccode\u003ESetup your account\u003C/code\u003E page.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003EIn this lab, we aim to show you how to predict the number of trips in the coming hours in each area of New York. To accomplish this, you will ingest the raw data and then aggregate it by hour and region. For simplicity, you will use \u003Ca href=\"https://www.uber.com/en-DE/blog/h3/\"\u003EDiscrete Global Grid H3\u003C/a\u003E. The result will be an hourly time series, each representing the count of trips originating from distinct areas. Before running prediction and visualizing results, you will enrich data with third-party signals, such as information about holidays and offline sports events.\u003C/p\u003E\n","\u003Cp\u003EIn this lab you will learn how to:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EWork with geospatial data\u003C/li\u003E\u003Cli\u003EEnrich data with new features\u003C/li\u003E\u003Cli\u003EPredict time-series of complex structure\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EThis approach is not unique to trip forecasting but is equally applicable in various scenarios where predictive analysis is required. Examples include forecasting scooter or bike pickups, food delivery orders, sales across multiple retail outlets, or predicting the volume of cash withdrawals across an ATM network. Such models are invaluable for planning and optimization across various industries and services.\u003C/p\u003E\n","\u003Ch3\u003EStep 1. Data acquisition\u003C/h3\u003E\n","\u003Cp\u003EThe New York Taxi and Limousine Commission (TLC) has provided detailed, anonymized customer travel data since 2009. Painted yellow cars can pick up passengers in any of the city's five boroughs. Raw data on yellow taxi rides can be found on the \u003Ca href=\"https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page\"\u003ETLC website\u003C/a\u003E. This data is divided into files by month. Each file contains detailed trip information, you can read about it \u003Ca href=\"https://www.nyc.gov/assets/tlc/downloads/pdf/data_dictionary_trip_records_yellow.pdf\"\u003Ehere\u003C/a\u003E. For our project, you will use an NY Taxi dataset for the 2014-2015 years from the \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9J2/carto-carto-academy-data-for-tutorials\"\u003ECARTO Academy\u003C/a\u003E Marketplace listing.\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ENavigate to the \u003Ccode\u003EMarketplace\u003C/code\u003E screen using the menu on the left side of the window\u003C/li\u003E\u003Cli\u003ESearch for \u003Ccode\u003ECARTO Academy\u003C/code\u003E in the search bar\u003C/li\u003E\u003Cli\u003EFind and click the \u003Ccode\u003ECARTO Academy - Data for tutorials\u003C/code\u003E tile\u003C/li\u003E\u003Cli\u003EOnce in the listing, click the big blue \u003Ccode\u003EGet\u003C/code\u003E button\u003C/li\u003E\u003C/ul\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EOn the \u003Ccode\u003EGet\u003C/code\u003E screen, you may be prompted to complete your \u003Ccode\u003Euser profile\u003C/code\u003E if you have not done so before. Click the link as shown in the screenshot below. Enter your name and email address into the profile screen and click the blue \u003Ccode\u003ESave\u003C/code\u003E button. You will be returned to the \u003Ccode\u003EGet\u003C/code\u003E screen.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_10.png\" alt=\"assets/geo_ml_10.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EAnother dataset you will use is events data and you can also get it from the Snowflake Marketplace. It is provided by PredictHQ and called \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZSTZ3TGTNLQM/predicthq-quickstart-demo\"\u003EPredictHQ Quickstart Demo\u003C/a\u003E.\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ESearch for\u003Ccode\u003EPredictHQ Quickstart Demo\u003C/code\u003E in the search bar\u003C/li\u003E\u003Cli\u003EFind and click the \u003Ccode\u003EQuickstart Demo\u003C/code\u003E tile\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_4.png\" alt=\"assets/geo_ml_4.png\"\u003E\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EOn the \u003Ccode\u003EGet Data\u003C/code\u003E screen click \u003Ccode\u003EGet\u003C/code\u003E.\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_6.png\" alt=\"assets/geo_ml_6.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ECongratulations! You have just created a shared database from a listing on the Snowflake Marketplace.\u003C/p\u003E\n","\u003Ch3\u003EStep 2. Data transformation\u003C/h3\u003E\n","\u003Cp\u003EIn this step, you'll divide New York into uniformly sized regions and assign each taxi pick-up location to one of these regions. We aim to get a table with the number of taxi trips per hour for each region.\u003C/p\u003E\n","\u003Cp\u003ETo achieve this division, you will use the Discrete Global Grid H3. H3 organizes the world into a grid of equal-sized hexagonal cells, with each cell identified by a unique code (either a string or an integer). This hierarchical grid system allows cells to be combined into larger cells or subdivided into smaller ones, facilitating efficient geospatial data processing.\u003C/p\u003E\n","\u003Cp\u003EH3 offers 16 different resolutions for dividing areas into hexagons, ranging from resolution 0, where the world is segmented into 122 large hexagons, to resolution 15. At this resolution, each hexagon is less than a square meter, covering the world with approximately 600 trillion hexagons. You can read more about resolutions \u003Ca href=\"https://h3geo.org/docs/core-library/restable/\"\u003Ehere\u003C/a\u003E. For our task, we will use resolution 8, where the size of each hexagon is about 0.7 sq. km (0.3 sq. miles).\u003C/p\u003E\n","\u003Cp\u003EAs a source of the trips data you will use \u003Ccode\u003ETLC_YELLOW_TRIPS_2014\u003C/code\u003E and \u003Ccode\u003ETLC_YELLOW_TRIPS_2015\u003C/code\u003E tables from the CARTO Academy listing. We are interested in the following fields:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EPickup Time\u003C/li\u003E\u003Cli\u003EDropoff Time\u003C/li\u003E\u003Cli\u003EPickup Latitude\u003C/li\u003E\u003Cli\u003EPickup Longitude\u003C/li\u003E\u003Cli\u003EDropoff Latitude\u003C/li\u003E\u003Cli\u003EDropoff Longitude\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EFirst, specify the default Database, Schema and the Warehouse:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EUSE ADVANCED_ANALYTICS.PUBLIC;\nUSE WAREHOUSE my_wh;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ESince CARTO's tables contain raw data you might want to clean it before storing. In the following query you will do a few data cleaning steps:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ERemove rows that are outside of latitude/longitude allowed values\u003C/li\u003E\u003Cli\u003EKeep only trips with a duration longer than one minute and distances more than 10 meters.\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EAnd since you are interested in trip data for 2014 and 2015 you need to union \u003Ccode\u003ETLC_YELLOW_TRIPS_2014\u003C/code\u003E and \u003Ccode\u003ETLC_YELLOW_TRIPS_2015\u003C/code\u003E tables. On average, the execution time on the LARGE warehouse is under 4 minutes.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides AS\nSELECT CONVERT_TIMEZONE('UTC', 'America/New_York', to_timestamp(PICKUP_DATETIME::varchar)) PICKUP_TIME,\n       CONVERT_TIMEZONE('UTC', 'America/New_York', to_timestamp(DROPOFF_DATETIME::varchar)) DROPOFF_TIME,\n       st_point(PICKUP_LONGITUDE, PICKUP_LATITUDE) AS PICKUP_LOCATION,\n       st_point(DROPOFF_LONGITUDE, DROPOFF_LATITUDE) AS DROPOFF_LOCATION,\nFROM CARTO_ACADEMY__DATA_FOR_TUTORIALS.CARTO.TLC_YELLOW_TRIPS_2014\nWHERE pickup_latitude BETWEEN -90 AND 90\n  AND dropoff_latitude BETWEEN -90 AND 90\n  AND pickup_longitude BETWEEN -180 AND 180\n  AND dropoff_longitude BETWEEN -180 AND 180\n  AND st_distance(st_point(PICKUP_LONGITUDE, PICKUP_LATITUDE), st_point(DROPOFF_LONGITUDE, DROPOFF_LATITUDE)) &gt; 10\n  AND TIMEDIFF(MINUTE, PICKUP_TIME, DROPOFF_TIME) &gt; 1\nUNION ALL\nSELECT CONVERT_TIMEZONE('UTC', 'America/New_York', to_timestamp(PICKUP_DATETIME::varchar)) PICKUP_TIME,\n       CONVERT_TIMEZONE('UTC', 'America/New_York', to_timestamp(DROPOFF_DATETIME::varchar)) DROPOFF_TIME,\n       st_point(PICKUP_LONGITUDE, PICKUP_LATITUDE) AS PICKUP_LOCATION,\n       st_point(DROPOFF_LONGITUDE, DROPOFF_LATITUDE) AS DROPOFF_LOCATION,\nFROM CARTO_ACADEMY__DATA_FOR_TUTORIALS.CARTO.TLC_YELLOW_TRIPS_2015\nWHERE pickup_latitude BETWEEN -90 AND 90\n  AND dropoff_latitude BETWEEN -90 AND 90\n  AND pickup_longitude BETWEEN -180 AND 180\n  AND dropoff_longitude BETWEEN -180 AND 180\n  AND st_distance(PICKUP_LOCATION, DROPOFF_LOCATION) &gt; 10\n  AND TIMEDIFF(MINUTE, PICKUP_TIME, DROPOFF_TIME) &gt; 1;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you will create a table where, for each pair of timestamp/H3, we calculate the number of trips. You will strip off minutes and seconds and keep only hours.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 AS\nSELECT TIME_SLICE(pickup_time, 60, 'minute', 'START') AS pickup_time,\n       H3_POINT_TO_CELL_string(pickup_location, 8) AS h3,\n       count(*) AS pickups\nFROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides\nGROUP BY 1, 2;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ESince on resolution 8, you might have more than 1000 hexagons for New York, to speed up the training process, you will keep only hexagons that had more than 1M pickups in 2014.  This is shown in the following code block.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 \nAS WITH all_hexagons AS\n  (SELECT h3,\n          SUM(pickups) AS total_pickups\n   FROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3\n   WHERE year(pickup_time) = 2014\n   GROUP BY 1)\nSELECT t1.*\nFROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 t1\nINNER JOIN all_hexagons t2 ON t1.h3 = t2.h3\nWHERE total_pickups &gt;= 1000000;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIt's important to remember that if the raw data lacks records for a specific hour and area combination, the aggregated data for that period should be marked as 0. This step is crucial for accurate time series prediction. Run the following query to add records indicating that there were zero trips for any H3 location and timestamp pair without recorded trips.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 AS\nWITH all_dates_hexagons AS (\n    SELECT DATEADD(HOUR, VALUE::int, '2014-01-01'::timestamp) AS pickup_time, h3\n    FROM TABLE(FLATTEN(ARRAY_GENERATE_RANGE(0, DATEDIFF('hour', '2014-01-01', '2015-12-31 23:59:00') + 1)))\n    CROSS JOIN (SELECT DISTINCT h3 FROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3)\n)\nSELECT t1.pickup_time, \nt1.h3, IFF(t2.pickups IS NOT NULL, t2.pickups, 0) AS pickups\nFROM all_dates_hexagons t1\nLEFT JOIN ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 t2 \nON t1.pickup_time = t2.pickup_time AND t1.h3 = t2.h3;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003EStep 4. Data Enrichment\u003C/h3\u003E\n","\u003Cp\u003EIn this step, you will enhance our dataset with extra features that could improve the accuracy of our predictions. Cortex model for time series automatically encodes days of the week as a separate feature, but it makes sense to consider that public or school holidays could affect the demand for taxi services. Likewise, areas hosting sporting events might experience a surge in taxi pickups. To incorporate this insight, you will use data from \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZSTZ3TGTNLQM/predicthq-quickstart-demo\"\u003EPredictHQ - Quickstart Demo\u003C/a\u003E listing, which provides information on events in New York for the years 2014-2015.\u003C/p\u003E\n","\u003Cp\u003ERun the following query to enrich the data with holiday, and event information. For sports events, you will include only those with a high rank.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 AS\nSELECT t1.*,\n       IFF(t2.category = 'school-holidays', 'school-holidays', 'None') AS school_holiday,\n       IFF(t3.category = 'public-holidays', ARRAY_TO_STRING(t3.labels, ', '), 'None') AS public_holiday,\n       IFF(t4.category = 'sports', t4.labels[0]::string, 'None') AS sport_event\nFROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 t1\nLEFT JOIN (SELECT distinct title, category, event_start, event_end, labels \n           FROM QUICKSTART_DEMO.PREDICTHQ.PREDICTHQ_EVENTS_SNOWFLAKE_SUMMIT_2024 \n           WHERE category = 'school-holidays' and title ilike 'New York%') t2 \n    ON DATE(t1.pickup_time) between t2.event_start AND t2.event_end\nLEFT JOIN (SELECT distinct title, category, event_start, event_end, labels \n           FROM QUICKSTART_DEMO.PREDICTHQ.PREDICTHQ_EVENTS_SNOWFLAKE_SUMMIT_2024 \n           WHERE array_contains('holiday-national'::variant, labels)) t3 \n    ON DATE(t1.pickup_time) between t3.event_start AND t3.event_end\nLEFT JOIN (SELECT * from QUICKSTART_DEMO.PREDICTHQ.PREDICTHQ_EVENTS_SNOWFLAKE_SUMMIT_2024 \n           WHERE phq_rank &gt; 70 and category = 'sports') t4 \n    ON t1.pickup_time = date_trunc('hour', t4.event_start) \n    AND t1.h3 = h3_point_to_cell_string(t4.geo, 8);\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003EStep 5. Training and Prediction\u003C/h3\u003E\n","\u003Cp\u003EIn this step, you'll divide our dataset into two parts: the Training set and the Prediction set. The Training set will be used to train our machine learning model. It will include data from the entirety of 2014 and part of 2015, going up to June 5th, 2015. Run the following query to create the Training set:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3_TRAIN AS\nSELECT *\nFROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3\nWHERE date(pickup_time) &lt; date('2015-06-05 12:00:00');\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThe prediction set, on the other hand, will contain data for one week starting June 5th, 2015. This setup allows us to make predictions on data that wasn't used during training.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3_PREDICT AS\nSELECT h3,\n       pickup_time,\n       SCHOOL_HOLIDAY,\n       PUBLIC_HOLIDAY,\n       SPORT_EVENT\nFROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3\nWHERE date(pickup_time) &gt;= date('2015-06-05')\nAND date(pickup_time) &lt; date('2015-06-12');\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow that you have the Training and Prediction sets, you can run your model training step. In this step, you will use Snowflake&rsquo;s Cortex ML Forecasting function to train your \u003Ccode\u003Eny_taxi_rides_model\u003C/code\u003E. You&rsquo;re telling the function it should train on \u003Ccode\u003Eny_taxi_rides_h3_train\u003C/code\u003E &ndash; and that this table contains data for multiple distinct time series (\u003Ccode\u003Eseries_colname =&gt; &lsquo;h3&rsquo;\u003C/code\u003E),  one for each h3 in the table. The function will now automatically train one machine learning model for each h3. Note that you are also telling the model which column in our table to use as a timestamp and which column to treat as our &ldquo;target&rdquo; (i.e., the column you want to forecast). On average the query below completes in about 7 minutes on the LARGE warehouse.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE snowflake.ml.forecast ny_taxi_rides_model(\n  input_data =&gt; system$reference('table', 'ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3_TRAIN'), \n  series_colname =&gt; 'h3', \n  timestamp_colname =&gt; 'pickup_time', \n  target_colname =&gt; 'pickups');\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you will predict the &quot;future&quot; demand for one week of test data. Run the following command to forecast demand for each H3 cell ID  and store your results in the &quot;forecasts&quot; table.\u003C/p\u003E\n","\u003Cp\u003ESimilar to what you did in the training step, you specify the data the model should use to generate its forecasts (\u003Ccode\u003Eny_taxi_rides_h3_predict\u003C/code\u003E) and indicate which columns to use for identifying unique H3 and for timestamps.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EBEGIN\n    CALL ny_taxi_rides_model!FORECAST(\n        INPUT_DATA =&gt; SYSTEM$REFERENCE('TABLE', 'ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3_PREDICT'),\n        SERIES_COLNAME =&gt; 'h3',\n        TIMESTAMP_COLNAME =&gt; 'pickup_time',\n        CONFIG_OBJECT =&gt; {'prediction_interval': 0.95}\n    );\n    -- These steps store your predictions to a table.\n    LET x := SQLID;\n    CREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_model_forecast AS \n    SELECT series::string as h3,\n    ts AS pickup_time,\n    -- If any forecasts or prediction intervals are negative you need to convert them to zero. \n    CASE WHEN forecast &lt; 0 THEN 0 ELSE forecast END AS forecast,\n    CASE WHEN lower_bound &lt; 0 THEN 0 ELSE lower_bound END AS lower_bound,\n    CASE WHEN upper_bound &lt; 0 THEN 0 ELSE upper_bound END AS upper_bound\n    FROM TABLE(RESULT_SCAN(:x));\nEND;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ECreate a table with predicted and actual results:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_compare AS\nSELECT t1.h3, \n       t1.pickup_time, \n       t2.pickups, \n       round(t1.forecast, 0) as forecast\nFROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_model_forecast t1\nINNER JOIN ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 t2\nON t1.h3 = t2.h3\nAND t1.pickup_time = t2.pickup_time;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you will generate evaluation metrics and store them in the \u003Ccode\u003Eny_taxi_rides_metrics\u003C/code\u003E table:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EBEGIN\n    CALL ny_taxi_rides_model!show_evaluation_metrics();\n    LET x := SQLID;\n    CREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_metrics AS \n    SELECT series::string as h3,\n           metric_value,\n           error_metric\n    FROM TABLE(RESULT_SCAN(:x));\nEND;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThe table \u003Ccode\u003Eny_taxi_rides_metrics\u003C/code\u003E contains various metrics; please review what is available in the table. You should select a metric that allows uniform comparisons across all hexagons to understand the model's performance in each hexagon. Since trip volumes may vary among hexagons, the chosen metric should not be sensitive to absolute values. The Symmetric Mean Absolute Percentage Error (\u003Ca href=\"https://en.wikipedia.org/wiki/Symmetric_mean_absolute_percentage_error\"\u003ESMAPE\u003C/a\u003E) would be a suitable choice. Create a table with the list of hexagons and the SMAPE value for each:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_metrics AS\nSELECT h3, metric_value AS smape \nFROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_metrics\nWHERE error_metric::string = 'SMAPE'\norder by 2 asc;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003EStep 6. Visualization and analysis\u003C/h3\u003E\n","\u003Cp\u003EIn this step, you will visualize the actual and predicted results and think on how you can improve our model. Open \u003Ccode\u003EProjects &gt; Streamlit &gt; + Streamlit App\u003C/code\u003E. Give the new app a name, for example \u003Ccode\u003EDemand Prediction - model analysis\u003C/code\u003E, and pick \u003Ccode\u003EADVANCED_ANALYTICS.PUBLIC\u003C/code\u003E as an app location.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_7.png\" alt=\"assets/geo_ml_7.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EClick on the packages tab and add \u003Ccode\u003Epydeck\u003C/code\u003E, \u003Ccode\u003Ebranca\u003C/code\u003E and \u003Ccode\u003Eplotly\u003C/code\u003E to the list of packages as our app will be using them.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_8.png\" alt=\"assets/geo_ml_8.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EThen copy-paste the following code to the editor and click \u003Ccode\u003ERun\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Eimport branca.colormap as cm\nimport datetime\nimport pandas as pd\nimport plotly.express as px\nimport pydeck as pdk\nimport streamlit as st\nfrom snowflake.snowpark.context import get_active_session\n\n@st.cache_data\ndef get_dataframe_from_raw_sql(query: str) -&gt; pd.DataFrame:\n    session = get_active_session()\n    pandas_df = session.sql(query).to_pandas()\n    return pandas_df\n\ndef pydeck_chart_creation(\n    chart_df: pd.DataFrame,\n    coordinates: tuple = (40.742, -73.984),\n    elevation_3d: bool = False,\n):\n    highest_count_df = 0 if chart_df is None else chart_df[&quot;COUNT&quot;].max()\n    st.image('https://sfquickstarts.s3.us-west-1.amazonaws.com/hol_geo_spatial_ml_using_snowflake_cortex/gradient.png')\n    st.pydeck_chart(\n        pdk.Deck(\n            map_style=None,\n            initial_view_state=pdk.ViewState(\n                latitude=coordinates[0],\n                longitude=coordinates[1],\n                pitch=45,\n                zoom=10,\n            ),\n            tooltip={&quot;html&quot;: &quot;&lt;b&gt;{H3}:&lt;/b&gt; {COUNT}&quot;, &quot;style&quot;: {&quot;color&quot;: &quot;white&quot;}},\n            layers=[\n                pdk.Layer(\n                    &quot;H3HexagonLayer&quot;,\n                    chart_df,\n                    get_hexagon=&quot;H3&quot;,\n                    get_fill_color=&quot;COLOR&quot;,\n                    get_line_color=&quot;COLOR&quot;,\n                    get_elevation=f&quot;COUNT/{highest_count_df}&quot;,\n                    auto_highlight=True,\n                    elevation_scale=10000 if elevation_3d else 0,\n                    pickable=True,\n                    elevation_range=[0, 300],\n                    extruded=True,\n                    coverage=1,\n                    opacity=0.3,\n                )\n            ],\n        )\n    )\n\ndef generate_linear_color_map(colors: list, quantiles):\n    return cm.LinearColormap(\n        colors,\n        vmin=quantiles.min(),\n        vmax=quantiles.max(),\n        index=quantiles,\n    )\n\ndef render_plotly_line_chart(chart_df: pd.DataFrame):\n    fig = px.line(\n        chart_df,\n        x=&quot;PICKUP_TIME&quot;,\n        y=[&quot;PICKUPS&quot;, &quot;FORECAST&quot;],\n        color_discrete_sequence=[&quot;#D966FF&quot;, &quot;#126481&quot;],\n        markers=True,\n    )\n\n    fig.update_layout(yaxis_title=&quot;Pickups&quot;, xaxis_title=&quot;&quot;)\n    st.plotly_chart(fig, theme=&quot;streamlit&quot;, use_container_width=True)\n\nst.set_page_config(layout=&quot;wide&quot;, initial_sidebar_state=&quot;expanded&quot;)\nst.title(&quot;NY Pickup Location App :balloon:&quot;)\nst.write(&quot;&quot;&quot;An app that visualizes geo-temporal data from NY taxi pickups using H3 and time series. \n\t\t\tIt can be useful to visualize marketplace signals that are distributed spatially and temporally.&quot;&quot;&quot;)\n\nAVGLATITUDELONGITUDE = &quot;&quot;&quot;SELECT\nAVG(ST_Y(H3_CELL_TO_POINT(h3))) AS lat,\nAVG(ST_X(h3_cell_to_point(h3))) AS lon,\nFROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_compare&quot;&quot;&quot;\n\nSQLQUERYTIMESERIES = &quot;&quot;&quot;SELECT pickup_time, h3, forecast, pickups\nFROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_compare&quot;&quot;&quot;\n\nSQLQUERYMETRICS = &quot;&quot;&quot;SELECT * FROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_metrics&quot;&quot;&quot;\n\ndf_avg_lat_long = get_dataframe_from_raw_sql(AVGLATITUDELONGITUDE)\navg_coordinate = (df_avg_lat_long.iloc[0, 0], df_avg_lat_long.iloc[0, 1])\ndf_metrics = get_dataframe_from_raw_sql(SQLQUERYMETRICS)\n\nwith st.sidebar:\n    initial_start_date = datetime.date(2015, 6, 6)\n    selected_date_range = st.date_input(\n        &quot;Date Range:&quot;,\n        (initial_start_date, initial_start_date + datetime.timedelta(days=7)),\n        format=&quot;MM.DD.YYYY&quot;,)\n\n    tr_col_l, tr_col_r = st.columns(2)\n    with tr_col_l:\n        selected_start_time_range = st.time_input(\n            &quot;Start Time Range&quot;,\n            datetime.time(0, 0),\n            key=&quot;selected_start_time_range&quot;,\n            step=3600,)\n    with tr_col_r:\n        selected_end_time_range = st.time_input(\n            &quot;End Time Range:&quot;,\n            datetime.time(23, 00),\n            key=&quot;selected_end_time_range&quot;,\n            step=3600,)\n    h3_options = st.selectbox(\n        &quot;H3 cells to display&quot;, ([&quot;All&quot;] + df_metrics[&quot;H3&quot;].to_list()))\n\n    with st.expander(&quot;:orange[Expand to see SMAPE metric]&quot;):\n        df_metrics_filtered = df_metrics\n        if h3_options != &quot;All&quot;:\n            df_metrics_filtered = df_metrics[df_metrics[&quot;H3&quot;] == h3_options]\n\n        st.dataframe(df_metrics_filtered, hide_index=True, width=300)\n    chckbox_3d_value = st.checkbox(\n        &quot;3D&quot;, key=&quot;chkbx_forecast&quot;, help=&quot;Renders H3 Hexagons in 3D&quot;)\n\nDF_PICKUPS = None\nDF_FORECAST = None\n\nstart_end_date_selected = len(selected_date_range) == 2\n\nif start_end_date_selected:\n    sql_query_pickups = f&quot;&quot;&quot;SELECT h3,\n    SUM(pickups) AS COUNT\n    FROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_compare\n    WHERE pickup_time BETWEEN DATE('{selected_date_range[0]}') AND DATE('{selected_date_range[1]}')\n    AND TIME(pickup_time) BETWEEN '{selected_start_time_range}' AND '{selected_end_time_range}'\n    GROUP BY 1&quot;&quot;&quot;\n\n    sql_query_forecast = f&quot;&quot;&quot;SELECT h3,\n    sum(forecast) AS COUNT\n    FROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_compare\n    WHERE pickup_time BETWEEN DATE('{selected_date_range[0]}') AND DATE('{selected_date_range[1]}')\n    AND TIME(pickup_time) BETWEEN '{selected_start_time_range}' AND '{selected_end_time_range}'\n    GROUP BY 1&quot;&quot;&quot;\n\n    colors_list = [&quot;gray&quot;, &quot;blue&quot;, &quot;green&quot;, &quot;yellow&quot;, &quot;orange&quot;, &quot;red&quot;]\n    DF_PICKUPS = get_dataframe_from_raw_sql(sql_query_pickups)\n    quantiles_pickups = DF_PICKUPS[&quot;COUNT&quot;].quantile([0, 0.25, 0.5, 0.75, 1])\n    color_map_pickups = generate_linear_color_map(colors_list, quantiles_pickups)\n    DF_PICKUPS[&quot;COLOR&quot;] = DF_PICKUPS[&quot;COUNT&quot;].apply(color_map_pickups.rgb_bytes_tuple)\n\n    DF_FORECAST = get_dataframe_from_raw_sql(sql_query_forecast)\n    quantiles_forecast = DF_FORECAST[&quot;COUNT&quot;].quantile([0, 0.25, 0.5, 0.75, 1])\n    color_map_forecast = generate_linear_color_map(colors_list, quantiles_forecast)\n    DF_FORECAST[&quot;COLOR&quot;] = DF_FORECAST[&quot;COUNT&quot;].apply(\n        color_map_forecast.rgb_bytes_tuple)\n\n    if h3_options != &quot;All&quot;:\n        DF_PICKUPS = DF_PICKUPS[DF_PICKUPS[&quot;H3&quot;] == h3_options]\n        DF_FORECAST = DF_FORECAST[DF_FORECAST[&quot;H3&quot;] == h3_options]\n\ncol1, col2 = st.columns(2)\nwith col1:\n    st.write(&quot;**Actual Demand**&quot;)\n    pydeck_chart_creation(DF_PICKUPS, avg_coordinate, chckbox_3d_value)\nwith col2:\n    st.write(&quot;**Forecasted Demand**&quot;)\n    pydeck_chart_creation(DF_FORECAST, avg_coordinate, chckbox_3d_value)\n\ndf_time_series = get_dataframe_from_raw_sql(SQLQUERYTIMESERIES)\nif DF_FORECAST is None or len(DF_FORECAST) == 0:\n    st.stop()\n\ncomparision_df_filter = (\n    (pd.to_datetime(df_time_series[&quot;PICKUP_TIME&quot;]).dt.date &gt;= selected_date_range[0])\n    &amp; (pd.to_datetime(df_time_series[&quot;PICKUP_TIME&quot;]).dt.date &lt;= selected_date_range[1])\n    &amp; (pd.to_datetime(df_time_series[&quot;PICKUP_TIME&quot;]).dt.time &gt;= selected_start_time_range)\n    &amp; (pd.to_datetime(df_time_series[&quot;PICKUP_TIME&quot;]).dt.time &lt;= selected_end_time_range))\n\nif h3_options == &quot;All&quot;:\n    st.markdown(&quot;### Comparison for All Hexagons&quot;)\n    df_time_series_filtered = (\n        df_time_series[comparision_df_filter]\n        .groupby([&quot;PICKUP_TIME&quot;], as_index=False)\n        .sum()\n    )\n    df_time_series_filtered = df_time_series_filtered[\n        [&quot;PICKUP_TIME&quot;, &quot;FORECAST&quot;, &quot;PICKUPS&quot;]\n    ]\n    with st.expander(&quot;Raw Data&quot;):\n        st.dataframe(df_time_series_filtered, use_container_width=True)\nelse:\n    st.markdown(f&quot;### Comparison for Hexagon ID {h3_options}&quot;)\n    df_time_series_filtered = (\n        df_time_series[(df_time_series[&quot;H3&quot;] == h3_options) &amp; comparision_df_filter]\n        .groupby([&quot;PICKUP_TIME&quot;], as_index=False)\n        .sum()\n    )\n    with st.expander(&quot;Raw Data&quot;):\n        st.dataframe(df_time_series_filtered, use_container_width=True)\n\nrender_plotly_line_chart(df_time_series_filtered)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EAfter clicking \u003Ccode\u003ERun\u003C/code\u003E button you will see the following UI:\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_14.png\" alt=\"assets/geo_ml_14.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EClick \u003Ccode\u003EExpand to see SMAPE metric\u003C/code\u003E in the sidebar and find hexagons with good/bad MAPE values. Find them on the map using \u003Ccode\u003EH3 cells to display\u003C/code\u003E dropdown.\u003C/p\u003E\n","\u003Cp\u003EAs you can see, overall, the model is quite good, with SMAPE below 0.3 for most of the hexagons. Even with its current quality, the model can already be used to predict future demand. However, let's still consider how you can improve it.\u003C/p\u003E\n","\u003Cp\u003EThe worst predictions are for hexagons corresponding to LaGuardia Airport (\u003Ccode\u003E882a100e25fffff\u003C/code\u003E, \u003Ccode\u003E882a100f57fffff\u003C/code\u003E, \u003Ccode\u003E882a100f53fffff\u003C/code\u003E). To address this, you might consider adding information about flight arrivals and departures, which could improve the model's quality. It is a bit surprising to see poor quality at the hexagon \u003Ccode\u003E882a100897fffff\u003C/code\u003E, which is close to Central Park. However, it seems that June 7th is the main driver of the poor prediction, as model significantly underpredicted during both day and night hours.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_9.png\" alt=\"assets/geo_ml_9.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EYou have information about public and school holidays and sports events among our features. Perhaps adding information about other local events, such as festivals, could improve the overall quality of the model.\u003C/p\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EThe code from this quickstart can be reused for other industries, such as food delivery, micro-mobility, retail, finance, etc. You might need to use different time intervals, third-party datasets, or quality metrics, but the idea and toolkit stay the same.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Ch2\u003ECustomer Reviews Sentiment Analysis\u003C/h2\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EBefore starting with this lab, complete the preparation steps from \u003Ccode\u003ESetup your account\u003C/code\u003E page.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EThis lab is \u003Ca href=\"https://github.com/Snowflake-Labs/sf-guide-geospatial-analytics-ai-ml\"\u003Eavailable\u003C/a\u003E as Snowflake Notebook.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003EThis lab will show you how to inject AI into your spatial analysis using Cortex Large Language Model (LLM) Functions to help you take your product and marketing strategy to the next level. Specifically, you&rsquo;re going to build a data application that gives food delivery companies the ability to explore the sentiments of customers in the Greater Bay Area. To do this, you use the Cortex LLM Complete Function to classify customer sentiment and extract the underlying reasons for that sentiment from a customer review. Then you use the Discrete \u003Ca href=\"https://www.uber.com/en-DE/blog/h3/\"\u003EGlobal Grid H3\u003C/a\u003E for visualizing and exploring spatial data.\u003C/p\u003E\n","\u003Ch3\u003EStep 1. Data acquisition\u003C/h3\u003E\n","\u003Cp\u003ETo complete the project you will use a synthetic dataset with delivery orders with the feedback for each order. We will simplify the task of data acquisition by putting the dataset in an S3 bucket, which you will connect as an external stage.\u003C/p\u003E\n","\u003Cp\u003EFirst specify the default Database, Schema and the Warehouse and create a file format that corresponds to the format of the trip and holiday data we stored in S3. Run the following queries:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EUSE ADVANCED_ANALYTICS.PUBLIC;\nUSE WAREHOUSE my_wh;\nCREATE OR REPLACE FILE FORMAT csv_format_nocompression TYPE = csv\nFIELD_OPTIONALLY_ENCLOSED_BY = '&quot;' FIELD_DELIMITER = ',' skip_header = 1;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you will create an external stage using S3 with test data:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE STAGE @ADVANCED_ANALYTICS.PUBLIC.AA_STAGE URL = 's3://sfquickstarts/hol_geo_spatial_ml_using_snowflake_cortex/';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThen create a table where you will store the customer feedback dataset:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS AS\nSELECT  $1::NUMBER as order_id,\n        $2::VARCHAR as customer_id,\n        TO_GEOGRAPHY($3) as delivery_location,\n        $4::NUMBER as delivery_postcode,\n        $5::FLOAT as delivery_distance_miles,\n        $6::VARCHAR as restaurant_food_type,\n        TO_GEOGRAPHY($7) as restaurant_location,\n        $8::NUMBER as restaurant_postcode,\n        $9::VARCHAR as restaurant_id,\n        $10::VARCHAR as review\nFROM @ADVANCED_ANALYTICS.PUBLIC.AA_STAGE/food_delivery_reviews.csv (file_format =&gt; 'csv_format_nocompression');\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ECongratulations!  Now you have \u003Ccode\u003Eorders_reviews\u003C/code\u003E table containing 100K orders with reviews.\u003C/p\u003E\n","\u003Ch3\u003EStep 2. Preparing and running the prompt\u003C/h3\u003E\n","\u003Cp\u003EIn this step, you will prepare the prompt to run the analysis. For the task at hand, you will use the CORTEX.COMPLETE ( ) function because it is purpose-built to power data processing and data generation tasks. First, let's create a cortex role. In the query below change the username AA to the username you used to login to Snowflake.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE ROLE cortex_user_role;\nGRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE cortex_user_role;\n\nGRANT ROLE cortex_user_role TO USER AA;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EYou are now ready to provide CORTEX.COMPLETE ( ) functions with the instructions on the analysis that you want to produce. Specifically, using a raw table with reviews you'll create a new table with two additional columns: Overall Sentiment and Sentiment Categories which are composed of two different CORTEX.COMPLETE ( ) prompts. For complex aspect-based sentiment analysis like this, you are going to pick the mixtral-8x7b, a very capable open-source LLM created by Mistral AI.\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003E\u003Cstrong\u003EOverall Sentiment\u003C/strong\u003E assigns an overall rating of the delivery: Very Positive, Positive, Neutral, Mixed, Negative, Very Negative, or other.\u003C/li\u003E\u003Cli\u003E\u003Cstrong\u003ESentiment Categories\u003C/strong\u003E give us richer insights into why the overall rating is based on Food Cost, Quality, and Delivery Time.\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EAs a general rule when writing a prompt, the instructions have to be simple, clear, and complete. For example, you will notice that you clearly define the task as classifying customer reviews into specific categories. It&rsquo;s important to define constraints of the desired output, otherwise the LLM will produce unexpected output. Below, you specifically tell the LLM to categorize anything it is not sure of as Other, and explicitly tell it to respond in JSON format.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT_TEST as\nSELECT TOP 10\n    order_id\n    , customer_id\n    , delivery_location\n    , delivery_postcode\n    , delivery_distance_miles\n    , restaurant_food_type\n    , restaurant_location\n    , restaurant_postcode\n    , restaurant_id\n    , review\n    , snowflake.cortex.complete('mixtral-8x7b'\n        , concat('You are a helpful data assistant and your job is to return a JSON formatted response that classifies a customer review (represented in the &lt;review&gt; section) as one of the following seven sentiment categories (represented in the &lt;categories&gt; section). Return your classification exclusively in the JSON format: {classification: &lt;&lt;value&gt;&gt;}, where &lt;&lt;value&gt;&gt; is one of the 7 classification categories in the section &lt;categories&gt;. \n        \n        &lt;categories&gt;\n        Very Positive\n        Positive\n        Neutral\n        Mixed \n        Negative \n        Very Negative\n        Other\n        &lt;/categories&gt;\n        \n        &quot;Other&quot; should be used for the classification if you are unsure of what to put. No other classifications apart from these seven in the &lt;categories&gt; section should be used.\n        \n        Here are some examples: \n            1. If review is: &quot;This place is awesome! The food tastes great, delivery was super fast, and the cost was cheap. Amazing!&quot;, then the output should only be {&quot;Classification&quot;: &quot;Very Positive&quot;}\n            2. If review is: &quot;Tried this new place and it was a good experience. Good food delivered fast.&quot;, then the output should only be {&quot;Classification&quot;: &quot;Positive&quot;}\n            3. If review is: &quot;Got food from this new joint. It was OK. Nothing special but nothing to complain about either&quot;, then the output should only be {&quot;Classification&quot;: &quot;Neural&quot;}\n            4. If review is: &quot;The pizza place we ordered from had the food delivered real quick and it tasted good. It just was pretty expensive for what we got.&quot;, then the output should only be {&quot;Classification&quot;: &quot;Mixed&quot;}\n            5. If review is: &quot;The hamburgers we ordered took a very long time and when they arrived they were just OK.&quot;, then the output should only be {&quot;Classification&quot;: &quot;Negative&quot;}\n            6. If review is: &quot;This food delivery experience was super bad. Overpriced, super slow, and the food was not that great. Disappointed.&quot;, then the output should only be {&quot;Classification&quot;: &quot;Very Negative&quot;}\n            7. If review is: &quot;An experience like none other&quot;, then the output should be &quot;{&quot;Classification&quot;: Other&quot;}\n        \n         It is very important that you do not return anything but the JSON formatted response. \n            \n        &lt;review&gt;', review, '&lt;/review&gt;\n        JSON formatted Classification Response: '\n                )\n    ) as sentiment_assessment   \n    , snowflake.cortex.complete(\n        'mixtral-8x7b'\n        , concat('You are a helpful data assistant. Your job is to classify customer input &lt;review&gt;. If you are unsure, return null. For a given category classify the sentiment for that category as: Very Positive, Positive, Mixed, Neutral, Negative, Very Negative. Respond exclusively in JSON format.\n\n        {\n        food_cost:\n        food_quality:\n        food_delivery_time:\n    \n        }\n      '  \n, review \n, 'Return results'\n        )) as sentiment_categories\nFROM \n    ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIf you look inside of \u003Ccode\u003EADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT_TEST\u003C/code\u003E you'll notice two new columns: \u003Ccode\u003Esentiment_assesment\u003C/code\u003E and \u003Ccode\u003Esentiment_categories\u003C/code\u003E. \u003Ccode\u003Esentiment_assesment\u003C/code\u003E contains overall assessment of the sentiment based on the review and \u003Ccode\u003Esentiment_categories\u003C/code\u003E has an evaluation of each of three components individually: cost, quality and delivery time.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_11.png\" alt=\"assets/geo_ml_11.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ENow when you see that the results stick to the expected format, you can run the query above without the \u003Ccode\u003Etop 10\u003C/code\u003E limit. This query might take some time to complete, so to save time for this quickstart we've ran it for you in advance and stored results which you can import into new table by running following two queries:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT (\n\tORDER_ID NUMBER(38,0),\n\tCUSTOMER_ID VARCHAR(16777216),\n\tDELIVERY_LOCATION GEOGRAPHY,\n\tDELIVERY_POSTCODE NUMBER(38,0),\n\tDELIVERY_DISTANCE_MILES FLOAT,\n\tRESTAURANT_FOOD_TYPE VARCHAR(16777216),\n\tRESTAURANT_LOCATION GEOGRAPHY,\n\tRESTAURANT_POSTCODE NUMBER(38,0),\n\tRESTAURANT_ID VARCHAR(16777216),\n\tREVIEW VARCHAR(16777216),\n\tSENTIMENT_ASSESSMENT VARCHAR(16777216),\n\tSENTIMENT_CATEGORIES VARCHAR(16777216)\n);\n\nCOPY INTO ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT\nFROM @ADVANCED_ANALYTICS.PUBLIC.AA_STAGE/food_delivery_reviews.csv\nFILE_FORMAT = (FORMAT_NAME = csv_format_nocompression);\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003EStep 3. Data transformation\u003C/h3\u003E\n","\u003Cp\u003ENow when you have a table with sentiment, you need to parse JSONs to store each component of the score into a separate column and convert the scoring provided by the LLM into numeric format, so you can easily visualize it. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT_ANALYSIS AS\nSELECT * exclude (food_cost, food_quality, food_delivery_time, sentiment) ,\n         CASE\n             WHEN sentiment = 'very positive' THEN 5\n             WHEN sentiment = 'positive' THEN 4\n             WHEN sentiment = 'neutral'\n                  OR sentiment = 'mixed' THEN 3\n             WHEN sentiment = 'negative' THEN 2\n             WHEN sentiment = 'very negative' THEN 1\n             ELSE NULL\n         END sentiment_score ,\n         CASE\n             WHEN food_cost = 'very positive' THEN 5\n             WHEN food_cost = 'positive' THEN 4\n             WHEN food_cost = 'neutral'\n                  OR food_cost = 'mixed' THEN 3\n             WHEN food_cost = 'negative' THEN 2\n             WHEN food_cost = 'very negative' THEN 1\n             ELSE NULL\n         END cost_score ,\n         CASE\n             WHEN food_quality = 'very positive' THEN 5\n             WHEN food_quality = 'positive' THEN 4\n             WHEN food_quality = 'neutral'\n                  OR food_quality = 'mixed' THEN 3\n             WHEN food_quality = 'negative' THEN 2\n             WHEN food_quality = 'very negative' THEN 1\n             ELSE NULL\n         END food_quality_score ,\n         CASE\n             WHEN food_delivery_time = 'very positive' THEN 5\n             WHEN food_delivery_time = 'positive' THEN 4\n             WHEN food_delivery_time = 'neutral'\n                  OR food_delivery_time = 'mixed' THEN 3\n             WHEN food_delivery_time = 'negative' THEN 2\n             WHEN food_delivery_time = 'very negative' THEN 1\n             ELSE NULL\n         END delivery_time_score\nFROM\n  (SELECT order_id ,\n          customer_id ,\n          delivery_location ,\n          delivery_postcode ,\n          delivery_distance_miles ,\n          restaurant_food_type ,\n          restaurant_location ,\n          restaurant_postcode ,\n          restaurant_id ,\n          review ,\n          try_parse_json(lower(sentiment_assessment)):classification::varchar AS sentiment ,\n          try_parse_json(lower(sentiment_categories)):food_cost::varchar AS food_cost ,\n          try_parse_json(lower(sentiment_categories)):food_quality::varchar AS food_quality ,\n          try_parse_json(lower(sentiment_categories)):food_delivery_time::varchar AS food_delivery_time\n   FROM ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT);\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Ch3\u003EStep 4. Data visualization\u003C/h3\u003E\n","\u003Cp\u003EIn this step, you will visualize the scoring results on the map. Open \u003Ccode\u003EProjects &gt; Streamlit &gt; + Streamlit App\u003C/code\u003E. Give the new app a name, for example \u003Ccode\u003ESentiment analysis - results\u003C/code\u003E, and pick \u003Ccode\u003EADVANCED_ANALYTICS.PUBLIC\u003C/code\u003E as an app location.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_12.png\" alt=\"assets/geo_ml_12.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EClick on the packages tab and add \u003Ccode\u003Epydeck\u003C/code\u003E and \u003Ccode\u003Ebranca\u003C/code\u003E to the list of packages as our app will be using them.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_8.png\" alt=\"assets/geo_ml_8.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EThen copy-paste the following code to the editor and click \u003Ccode\u003ERun\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Efrom snowflake.snowpark.context import get_active_session\nfrom typing import Tuple\nimport branca.colormap as cm\nimport pandas as pd\nimport pydeck as pdk\nimport streamlit as st\n\n@st.cache_data\ndef get_dataframe_from_raw_sql(query: str) -&gt; pd.DataFrame:\n    session = get_active_session()\n    pandas_df = session.sql(query).to_pandas()\n    return pandas_df\n\ndef get_h3_df_orders_quantiles(resolution: float, type_of_location: str) -&gt; pd.DataFrame:\n    df = get_dataframe_from_raw_sql(\n        f&quot;&quot;&quot;SELECT\n        H3_POINT_TO_CELL_STRING(to_geography({ type_of_location }), { resolution }) AS h3,\n        round(count(*),2) as count\n        FROM ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT_ANALYSIS\n        GROUP BY 1&quot;&quot;&quot;)\n\n    quantiles = get_quantile_in_column(df, &quot;COUNT&quot;)\n    return df, quantiles\n\ndef get_h3_df_sentiment_quantiles(\n    resolution: float, type_of_sentiment: str, type_of_location: str\n) -&gt; Tuple[pd.DataFrame, pd.core.series.Series]:\n    df = get_dataframe_from_raw_sql(\n        f&quot;&quot;&quot; SELECT \n        H3_POINT_TO_CELL_STRING(TO_GEOGRAPHY({ type_of_location }),{ resolution }) AS h3,\n        round(AVG({ type_of_sentiment }),2) AS count\n        FROM ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT_ANALYSIS\n        WHERE { type_of_sentiment } IS NOT NULL \n        GROUP BY 1&quot;&quot;&quot;)\n\n    quantiles = get_quantile_in_column(df, &quot;COUNT&quot;)\n    df = df[(df[&quot;COUNT&quot;] &gt;= values[0]) &amp; (df[&quot;COUNT&quot;] &lt;= values[1])]\n    return df, quantiles\n\ndef get_h3_layer(layer_dataframe: pd.DataFrame, elevation_3d: bool = False,) -&gt; pdk.Layer:\n    highest_count_df = 0 if layer_dataframe is None else layer_dataframe[&quot;COUNT&quot;].max()\n    return pdk.Layer(\n        &quot;H3HexagonLayer&quot;,\n        layer_dataframe,\n        get_hexagon=&quot;H3&quot;,\n        get_fill_color=&quot;COLOR&quot;,\n        get_line_color=&quot;COLOR&quot;,\n        auto_highlight=True,\n        get_elevation=f&quot;COUNT/{highest_count_df}&quot;,\n        elevation_scale=10000 if elevation_3d else 0,\n        elevation_range=[0, 300],\n        pickable=True,\n        opacity=0.5,\n        extruded=True)\n\ndef get_quantile_in_column(\n    quantile_dataframe: pd.DataFrame, column_name: str\n) -&gt; pd.core.series.Series:\n    return quantile_dataframe[column_name].quantile([0, 0.25, 0.5, 0.75, 1])\n\ndef render_pydeck_chart(\n    chart_quantiles: pd.core.series.Series, \n    chart_dataframe: pd.DataFrame, \n    elevation_3d: bool = False):\n    colors = [&quot;gray&quot;, &quot;blue&quot;, &quot;green&quot;, &quot;yellow&quot;, &quot;orange&quot;, &quot;red&quot;]\n    color_map = cm.LinearColormap(\n        colors,\n        vmin=chart_quantiles.min(),\n        vmax=chart_quantiles.max(),\n        index=chart_quantiles)\n    chart_dataframe[&quot;COLOR&quot;] = chart_dataframe[&quot;COUNT&quot;].apply(color_map.rgb_bytes_tuple)\n    st.pydeck_chart(\n        pdk.Deck(\n            map_provider=&quot;mapbox&quot;,\n            map_style=&quot;light&quot;,\n            initial_view_state=pdk.ViewState(\n                latitude=37.633,\n                longitude=-122.284,\n                zoom=7,\n                pitch=50 if elevation_3d else 0,\n                height=430),\n            tooltip={&quot;html&quot;: &quot;&lt;b&gt;Value:&lt;/b&gt; {COUNT}&quot;,\n            &quot;style&quot;: {&quot;color&quot;: &quot;white&quot;}},\n            layers=get_h3_layer(chart_dataframe, elevation_3d)))\n\nst.set_page_config(layout=&quot;centered&quot;, initial_sidebar_state=&quot;expanded&quot;)\nst.title(&quot;Reviews of Food Delivery Orders&quot;)\n\nwith st.sidebar:\n    h3_resolution = st.slider(&quot;H3 resolution&quot;, min_value=6, max_value=9, value=7)\n    type_of_locations = st.selectbox(&quot;Dimensions&quot;, (&quot;DELIVERY_LOCATION&quot;, &quot;RESTAURANT_LOCATION&quot;), index=0)\n    type_of_data = st.selectbox(\n        &quot;Measures&quot;,(&quot;ORDERS&quot;,&quot;SENTIMENT_SCORE&quot;,&quot;COST_SCORE&quot;,&quot;FOOD_QUALITY_SCORE&quot;,&quot;DELIVERY_TIME_SCORE&quot;), index=0)\n    if type_of_data != &quot;ORDERS&quot;:\n        values = st.slider(&quot;Select a range for score values&quot;, 0.0, 5.0, (0.0, 5.0))\n        chckbox_3d_value = False\n    else:\n        chckbox_3d_value = st.checkbox(&quot;3D&quot;, key=&quot;chkbx_forecast&quot;, help=&quot;Renders H3 Hexagons in 3D&quot;)\n\nif type_of_data != &quot;ORDERS&quot;:\n    df, quantiles = get_h3_df_sentiment_quantiles(h3_resolution, type_of_data, type_of_locations)\n\nif type_of_data == &quot;ORDERS&quot;:\n    df, quantiles = get_h3_df_orders_quantiles(h3_resolution, type_of_locations)\n\nst.image(&quot;https://sfquickstarts.s3.us-west-1.amazonaws.com/hol_geo_spatial_ml_using_snowflake_cortex/gradient.png&quot;)\n\nrender_pydeck_chart(quantiles, df, chckbox_3d_value)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EAfter clicking \u003Ccode\u003ERun\u003C/code\u003E button you will see the following UI:\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_13.png\" alt=\"assets/geo_ml_13.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EYou can start with the overall analysis of the order density. When you select &quot;DELIVERY_LOCATION&quot; as a Dimension and &quot;ORDERS&quot; as a Measure you'll see what areas correspond to the high number of orders. You can use scale 7 and zoom in to identify clear clusters of where the most deliveries are occurring. In this case you see most deliveries are in Santa Clara, San Jose, and the San Francisco Bay. In particular, the area on the San Francisco peninsula looks to be an area of interest. Zooming in further you can see a dense area of delivery orders.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_15.png\" alt=\"assets/geo_ml_15.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EUsing a finer H3 resolution, 8 shows how the delivery densities are distributed more finely. From this resolution, you can see the orders are concentrated in Daly City and proceed down to San Bruno. Additionally, in the North, the majority of the orders are coming from the stretch of the Sunset District to the Mission District.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_16.png\" alt=\"assets/geo_ml_16.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ENow that you know where the majority of orders are coming from, let's analyze whether there are interesting differences in customer satisfaction depending on where they are located. Select DELIVERY LOCATION as a dimension and SENTIMENT_SCORE as a Measure to see the overall sentiment score that the Cortex LLM Complete Function generated. You can notice that the customers are mostly satisfied in the areas of Daly City down to San Jose, in the Santa Rosa area, and around Dublin. You also see that the area between these is mostly showing unhappy customers.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_17.png\" alt=\"assets/geo_ml_17.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EIn order to understand why customers in this area are unhappy, you analyze the aspect based sentiment results of the Cortex LLM Complete Function generated  for the categories of interest: food cost, delivery time, and the food quality. If you focus purely on the customers that were unhappy, you see that the primary reasons are food quality and food cost getting poor scores. Essentially, the food is not worth the cost and delivery time being fast does not make up for this. Check visualizations using the following combinations of parameters:\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_18.png\" alt=\"assets/geo_ml_18.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EIf you look at all H3 cells where food quality was high, the average sentiment score is also generally high. Again, you can see there are no cells where customers felt the food quality was above average in the greater Berkeley area. This could indicate either that high quality delivery food is uncommon or that the customers in these areas have higher expectations for delivery food.\u003C/p\u003E\n","\u003Cp\u003EYou can also analyze what areas are getting higher scores for each of the categories and how it correlates with the overall sentiment scores for restaurants in each area.\u003C/p\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EThe code from this quickstart can be reused for other industries, such as urban mobility, retail, finance, etc. Basically, any industry that involves providing a service with geo components and customer reviews.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Ch2\u003EProcessing unstructured geospatial data\u003C/h2\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EBefore starting with this lab, complete the preparation steps from \u003Ccode\u003ESetup your account\u003C/code\u003E page.\u003C/p\u003E\n\u003C/blockquote\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EThis lab is \u003Ca href=\"https://github.com/Snowflake-Labs/sf-guide-geospatial-analytics-ai-ml\"\u003Eavailable\u003C/a\u003E as Snowflake Notebook.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003EIn this quickstart guide, we will show you how to read geospatial data from unstructured sources such as GeoTiffs and Shapefiles to prepare features for a machine learning model using Snowflake and popular Python geospatial libraries.\u003C/p\u003E\n","\u003Cp\u003EYou will learn how to join data from different sources to help predict the presence of groundwater. Although the prediction step itself is out of scope for this lab, you will learn how to ingest data from raster files and shapefiles and combine them using nearest neighbour approach.\u003C/p\u003E\n","\u003Cp\u003EIn this lab, we will use the following sources:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EElevation map from \u003Ca href=\"https://www.usgs.gov/\"\u003EUnited States Geological Survey\u003C/a\u003E.\u003C/li\u003E\u003Cli\u003EAverage precipitation and average temperature data from \u003Ca href=\"https://www.worldclim.org/data/worldclim21.html\"\u003EWorldClim\u003C/a\u003E.\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EThe result of this lab will be a single dataset containing information derived from the above sources.\u003C/p\u003E\n","\u003Cp\u003ESince we will be running relatively complex computations using Snowpark, you will need a \u003Ccode\u003ELARGE\u003C/code\u003E Snowpark-optimized warehouse. Run the following query to create one:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE WAREHOUSE IF NOT EXISTS snowpark_opt_wh_l WITH warehouse_size = 'LARGE' warehouse_type = 'SNOWPARK-OPTIMIZED';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you can go to notebook settings and set the newly created warehouse as the \u003Ccode\u003ESQL warehouse\u003C/code\u003E. Additionally, go to the Packages dropdown and import \u003Ccode\u003Ebranca\u003C/code\u003E, \u003Ccode\u003Epydeck\u003C/code\u003E and \u003Ccode\u003Erasterio\u003C/code\u003E, which you will use in this lab.\u003C/p\u003E\n","\u003Ch3\u003EStep 1. Data Acquisition\u003C/h3\u003E\n","\u003Cp\u003EAs a first step, you will attach an external stage with raster and shapefiles. Run the following queries:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE DATABASE IF NOT EXISTS ADVANCED_ANALYTICS;\nCREATE SCHEMA IF NOT EXISTS ADVANCED_ANALYTICS.RASTER;\nUSE SCHEMA ADVANCED_ANALYTICS.RASTER;\n\nCREATE OR REPLACE STAGE ADVANCED_ANALYTICS.RASTER.FILES URL = 's3://sfquickstarts/hol_geo_spatial_ml_using_snowflake_cortex/unstructured/';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EFor this lab, you will also use a native application called \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZTYZF0RTY3/wherobots-sedonasnow\"\u003ESedonaSnow\u003C/a\u003E, which contains more than a hundred geospatial functions.\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ENavigate to the \u003Ccode\u003EMarketplace\u003C/code\u003E screen using the menu on the left side of the window.\u003C/li\u003E\u003Cli\u003ESearch for \u003Ccode\u003ESedonaSnow\u003C/code\u003E in the search bar.\u003C/li\u003E\u003Cli\u003EOnce in the listing, click the big blue \u003Ccode\u003EGet\u003C/code\u003E button.\u003C/li\u003E\u003C/ul\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EOn the \u003Ccode\u003EGet\u003C/code\u003E screen, you may be prompted to complete your user profile if you have not done so before. Click the link as shown in the screenshot below. Enter your name and email address into the profile screen and click the blue Save button. You will be returned to the \u003Ccode\u003EGet\u003C/code\u003E screen.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_22.png\" alt=\"assets/geo_ml_22.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ECongratulations, you have now acquired all the data sources that you need for this lab.\u003C/p\u003E\n","\u003Ch3\u003EStep 2. Loading Raster Data\u003C/h3\u003E\n","\u003Cp\u003EIn this step, you will load data from raster files stored in an external stage and store it as a Snowflake table.\u003C/p\u003E\n","\u003Cp\u003EYou will start with elevation data. Let's first create a function that uses the Python library \u003Ccode\u003Erasterio\u003C/code\u003E, available in the \u003Ca href=\"https://repo.anaconda.com/pkgs/snowflake/\"\u003ESnowflake Conda Channel\u003C/a\u003E, which reads metadata from a \u003Ccode\u003EGeoTiff\u003C/code\u003E file stored in a stage. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_EXTRACT_GEOTIFF_METADATA(PATH_TO_FILE STRING)\nRETURNS TABLE (\n    status BOOLEAN,\n    error STRING,\n    band_count INT,\n    crs STRING,\n    bounds STRING,\n    metadata STRING\n)\nLANGUAGE PYTHON\nRUNTIME_VERSION = '3.8'\nPACKAGES = ('rasterio', 'snowflake-snowpark-python')\nHANDLER = 'GeoTiffMetadataExtractor'\nAS $$\nimport rasterio\nimport json\nfrom snowflake.snowpark.files import SnowflakeFile\n\nclass GeoTiffMetadataExtractor:\n    def process(self, PATH_TO_FILE: str):\n        try:\n            # Initialize the result variables\n            status = False\n            error = ''\n            band_count = None\n            crs = None\n            bounds_json = None\n            metadata_json = None\n\n            # Read the GeoTIFF file from the specified stage path into memory\n            with SnowflakeFile.open(PATH_TO_FILE, 'rb', require_scoped_url=False) as input_file:\n                tif_bytes = input_file.read()\n\n            # Use rasterio's MemoryFile to read the TIFF data from memory\n            with rasterio.MemoryFile(tif_bytes) as memfile:\n                with memfile.open() as dataset:\n                    # Extract metadata from the dataset\n                    band_count = dataset.count\n                    crs = str(dataset.crs)  # Convert CRS to string for serialization\n                    bounds = dataset.bounds._asdict()  # Convert bounds to a dictionary\n\n                    # Ensure that metadata is serializable\n                    metadata = dataset.meta.copy()\n                    # Convert 'transform' to a tuple\n                    if 'transform' in metadata:\n                        metadata['transform'] = metadata['transform'].to_gdal()\n                    # Convert 'crs' to string\n                    if 'crs' in metadata:\n                        metadata['crs'] = str(metadata['crs'])\n\n                    # Convert bounds and metadata to JSON strings\n                    bounds_json = json.dumps(bounds)\n                    metadata_json = json.dumps(metadata)\n\n                    # Parsing successful\n                    status = True\n\n        except Exception as e:\n            # Handle exceptions, such as corrupted files\n            error = str(e)\n            status = False\n\n        # Yield the result as a single row\n        yield (\n            status,\n            error,\n            band_count,\n            crs,\n            bounds_json,\n            metadata_json\n        )\n$$;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EAdditionally, you will create a function to check the distribution of bands. Sometimes, bands of certain values prevail over others, and stripping them off during loading of data from raster files can significantly reduce the size of the table.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_RASTER_BAND_VALUE_STATS(PATH_TO_FILE STRING)\nRETURNS TABLE (\n    band_value FLOAT,\n    count BIGINT,\n    percentage FLOAT\n)\nLANGUAGE PYTHON\nRUNTIME_VERSION = '3.8'\nPACKAGES = ('numpy', 'rasterio', 'snowflake-snowpark-python')\nHANDLER = 'RasterBandValueStats'\nAS $$\nimport numpy as np\nimport rasterio\nfrom snowflake.snowpark.files import SnowflakeFile\n\nclass RasterBandValueStats:\n    def process(self, PATH_TO_FILE: str):\n        try:\n            # Read the GeoTIFF file from the specified stage path\n            with SnowflakeFile.open(PATH_TO_FILE, 'rb', require_scoped_url=False) as input_file:\n                tif_bytes = input_file.read()  # Read the entire file into bytes\n\n            # Use rasterio's MemoryFile to read the TIFF data from memory\n            with rasterio.MemoryFile(tif_bytes) as memfile:\n                with memfile.open() as dataset:\n                    # Read all bands into a NumPy array\n                    data = dataset.read()  # Shape: (band_count, rows, cols)\n\n                    # Flatten the data across all bands\n                    data_flat = data.flatten()  # 1D array of all pixel values across all bands\n\n                    # Count unique values\n                    unique_values, counts = np.unique(data_flat, return_counts=True)\n\n                    # Calculate total number of values\n                    total_count = data_flat.size\n\n                    # Calculate percentage for each unique value\n                    percentages = (counts / total_count) * 100\n\n                    # Yield results\n                    for value, count, percentage in zip(unique_values, counts, percentages):\n                        yield (\n                            float(value),\n                            int(count),\n                            round(float(percentage),1)\n                        )\n        except Exception as e:\n            raise Exception(f&quot;Error during data extraction: {e}&quot;)\n$$;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENext, you will create a function that reads data from a \u003Ccode\u003EGeoTIFF\u003C/code\u003E file and outputs it as a table. The UDF you will create processes each pixel in the \u003Ccode\u003EGeoTIFF\u003C/code\u003E file by calculating the spatial coordinates (X and Y) of the centroid of the pixel. It then associates these coordinates with the pixel's corresponding band values.\u003C/p\u003E\n","\u003Cp\u003EThis approach transforms the raster image into a collection of spatial points enriched with attribute data (band values), making it suitable for vector-based analyses and database operations. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOTIFF(\n    PATH_TO_FILE STRING,\n    SKIP_VALUES ARRAY DEFAULT NULL  -- Make SKIP_VALUES optional with default NULL\n)\nRETURNS TABLE (\n    x FLOAT,\n    y FLOAT,\n    band_values ARRAY,\n    band_count INT\n)\nLANGUAGE PYTHON\nRUNTIME_VERSION = '3.8'\nPACKAGES = ('numpy', 'rasterio', 'snowflake-snowpark-python')\nHANDLER = 'GeoTiffExtractor'\nAS $$\nimport numpy as np\nimport rasterio\nfrom snowflake.snowpark.files import SnowflakeFile\n\nclass GeoTiffExtractor:\n    def process(self, PATH_TO_FILE: str, SKIP_VALUES=None):\n        try:\n            # Read the GeoTIFF file from the specified stage path\n            with SnowflakeFile.open(PATH_TO_FILE, 'rb', require_scoped_url=False) as input_file:\n                tif_bytes = input_file.read()  # Read the entire file into bytes\n\n            # Use rasterio's MemoryFile to read the TIFF data from memory\n            with rasterio.MemoryFile(tif_bytes) as memfile:\n                with memfile.open() as dataset:\n                    # Read all bands into a NumPy array\n                    data = dataset.read()  # Shape: (band_count, rows, cols)\n\n                    # Get the number of bands\n                    band_count = data.shape[0]\n\n                    # Get the coordinates\n                    rows, cols = np.indices((dataset.height, dataset.width))\n                    xs, ys = rasterio.transform.xy(\n                        dataset.transform, rows, cols, offset='center'\n                    )\n\n                    # Flatten the arrays\n                    xs = np.array(xs).flatten()\n                    ys = np.array(ys).flatten()\n                    pixel_values = data.reshape((band_count, -1)).T  # Shape: (num_pixels, band_count)\n\n                    # Handle SKIP_VALUES\n                    if SKIP_VALUES:\n                        # Convert SKIP_VALUES to a NumPy array for efficient comparison\n                        skip_values = np.array(SKIP_VALUES)\n\n                        # Create a mask for pixels to skip\n                        skip_mask = np.isin(pixel_values, skip_values).any(axis=1)\n                        # Invert the skip_mask to get the mask of pixels to keep\n                        mask = ~skip_mask\n\n                        # Apply the mask to xs, ys, and pixel_values\n                        xs_filtered = xs[mask]\n                        ys_filtered = ys[mask]\n                        pixel_values_filtered = pixel_values[mask]\n                    else:\n                        # If SKIP_VALUES not provided, use all data\n                        xs_filtered = xs\n                        ys_filtered = ys\n                        pixel_values_filtered = pixel_values\n\n                    # For each pixel, yield a row with x, y, and band values\n                    for i in range(len(xs_filtered)):\n                        # Get the pixel values for all bands\n                        band_vals = pixel_values_filtered[i].tolist()\n                        yield (\n                            xs_filtered[i],\n                            ys_filtered[i],\n                            band_vals,\n                            band_count\n                        )\n        except Exception as e:\n            raise Exception(f&quot;Error during data extraction: {e}&quot;)\n$$;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you will check the metadata of the elevation file. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT *\nFROM table(PY_EXTRACT_GEOTIFF_METADATA(build_scoped_file_url(@FILES,'ASTGTMV003_N07E033_dem.tif')));\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EAs you can see, the GeoTiff uses reference system \u003Ccode\u003EEPSG:4326\u003C/code\u003E which means you can store it as \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E type. Run the following query to check how bands are distributed inside of the raster.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT *\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_RASTER_BAND_VALUE_STATS(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'ASTGTMV003_N07E033_dem.tif')));\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThere are no obvious outliers among band values&mdash;those that correspond to most of the raster points. In this case, let's load data from the whole \u003Ccode\u003EASTGTMV003_N07E033_dem.tif\u003C/code\u003E into the table \u003Ccode\u003EPOC.RASTER.AFRICA_ELEVATION\u003C/code\u003E:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION AS\nSELECT st_makepoint(x, y) as geog,\nband_values[0]::float as band\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOTIFF(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'ASTGTMV003_N07E033_dem.tif')));\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ELet's check the size of the newly created table. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT count(*) FROM ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E12,967,201 rows. The number of rows is a product of width and height in pixels. In the case of the elevation file, it's 3601&times;3601. Some raster files might be quite large, and to process them, it might be a good idea to use Snowpark-optimized warehouses to avoid memory exhaustion issues. Another technique that you could apply is to load not all points from the raster file but only those that contain useful information. Alternatively, you can resample large files to reduce their resolution. We will show you an example of how to do this at the end of this lab.\u003C/p\u003E\n","\u003Cp\u003EBut 13M rows is also a rather large table, and visualizing its results using Python libraries might be challenging without reducing the number of rows. H3 functions can help with that. In the code below, you will do the following:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EMap each point from \u003Ccode\u003EPOC.RASTER.AFRICA_ELEVATION\u003C/code\u003E to an H3 cell with resolution 8.\u003C/li\u003E\u003Cli\u003EGroup by H3 Cell ID and calculate the average value of the band for each H3 cell.\u003C/li\u003E\u003Cli\u003EVisualize the H3 cells, using the band as the source for color coding.\u003C/li\u003E\u003C/ul\u003E\n\u003Cpre\u003E\u003Ccode\u003Eimport streamlit as st\nimport pandas as pd\nimport pydeck as pdk\nfrom typing import List\nimport branca.colormap as cm\nfrom snowflake.snowpark.context import get_active_session\n\nsession = get_active_session()\n\n# Execute the updated SQL query\ndf = session.sql('''select h3_point_to_cell_string(geog, 8) as h3_cell,\n                    st_x(h3_cell_to_point(h3_cell)) as lon,\n                    st_y(h3_cell_to_point(h3_cell)) as lat,\n                    avg(band) as band \n                    from ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION\n                    group by all;''').to_pandas()\n\ndf[&quot;BAND&quot;] = df[&quot;BAND&quot;].apply(lambda row: float(row))\ncenter_latitude = df['LAT'].mean()\ncenter_longitude = df['LON'].mean()\n\ndef get_quantiles(df_column: pd.Series, quantiles: List) -&gt; pd.Series:\n    return df_column.quantile(quantiles)\n\ndef get_color(df_column: pd.Series, colors: List, vmin: int, vmax: int, index: pd.Series) -&gt; pd.Series:\n    color_map = cm.LinearColormap(colors, vmin=vmin, vmax=vmax, index=index)\n    return df_column.apply(color_map.rgb_bytes_tuple)\n    \nquantiles = get_quantiles(df[&quot;BAND&quot;], [0, 0.2, 0.4, 0.6, 0.8, 1])\ncolors = ['gray','blue','green','yellow','orange','red']\n\ndf['BAND'] = get_color(df['BAND'], colors, quantiles.min(), quantiles.max(), quantiles)\n\nst.pydeck_chart(pdk.Deck(\n    map_style=None,\n    initial_view_state=pdk.ViewState(\n        latitude=center_latitude,\n        longitude=center_longitude, \n        zoom=8.7, \n        bearing=0, \n        pitch=0),\n    layers=[\n        pdk.Layer(\n            &quot;H3HexagonLayer&quot;,\n            df,\n            opacity=0.9,\n            stroked=False,\n            get_hexagon=&quot;H3_CELL&quot;,\n            get_fill_color='BAND',\n            extruded=False,\n            wireframe=True,\n            line_width_min_pixels=0,\n            auto_highlight=True,\n            pickable=False,\n            filled=True\n        )\n    ],\n))\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_23.png\" alt=\"assets/geo_ml_23.png\"\u003E\u003C/p\u003E\n","\u003Ch3\u003EStep 3. Load Shapefile\u003C/h3\u003E\n","\u003Cp\u003EIn this step you will load precipitation and average temperature data from a Shapefile. First, you will create a UDF that uses \u003Ca href=\"https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-examples#reading-a-dynamically-specified-file-with-snowflakefile\"\u003EDynamic file Access\u003C/a\u003E and \u003Ccode\u003Efiona\u003C/code\u003E library to read metadata from Shapefile. Run the following code:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOFILE_METADATA(PATH_TO_FILE string, filename string)\nRETURNS TABLE (metadata variant)\nLANGUAGE python\nRUNTIME_VERSION = 3.8\nPACKAGES = ('fiona', 'snowflake-snowpark-python')\nHANDLER = 'GeoFileReader'\nAS $$\n# Import necessary modules for file handling and geospatial data processing\nfrom snowflake.snowpark.files import SnowflakeFile\nfrom fiona.io import ZipMemoryFile\nimport fiona\n\n# Helper function to make objects JSON-serializable\ndef make_serializable(obj):\n    if isinstance(obj, dict):\n        # Recursively process dictionary items\n        return {k: make_serializable(v) for k, v in obj.items()}\n    elif isinstance(obj, (list, tuple)):\n        # Recursively process lists and tuples\n        return [make_serializable(v) for v in obj]\n    elif isinstance(obj, (int, float, str, bool, type(None))):\n        # Base case: object is already serializable\n        return obj\n    else:\n        # Convert non-serializable objects to strings\n        return str(obj)\n\n# Define the handler class for the UDF\nclass GeoFileReader:\n    def process(self, PATH_TO_FILE: str, filename: str):\n        # Enable support for KML drivers in Fiona\n        fiona.drvsupport.supported_drivers['libkml'] = 'rw'\n        fiona.drvsupport.supported_drivers['LIBKML'] = 'rw'\n\n        # Open the file from the Snowflake stage in binary read mode\n        with SnowflakeFile.open(PATH_TO_FILE, 'rb') as f:\n            # Read the zip file into memory using Fiona's ZipMemoryFile\n            with ZipMemoryFile(f) as zip:\n                # Open the specified file within the zip archive\n                with zip.open(filename) as collection:\n                    # Extract metadata from the collection\n                    metadata = {\n                        'driver': collection.driver,  # File format driver (e.g., 'ESRI Shapefile')\n                        'crs': collection.crs.to_string() if collection.crs else None,  \n                        'schema': collection.schema,  # Schema of the data (fields and types)\n                        'bounds': collection.bounds,  # Spatial bounds of the dataset\n                        'meta': collection.meta,      # Additional metadata\n                        'name': collection.name,      # Name of the collection\n                        'encoding': collection.encoding,  # Character encoding of the file\n                        'length': len(collection),    # Number of features in the dataset\n                    }\n                    # Ensure the metadata is serializable to JSON\n                    serializable_metadata = make_serializable(metadata)\n                    # Yield the metadata as a tuple (required for UDFs returning TABLE)\n                    yield (serializable_metadata,)\n$$;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThe UDF above can be used not only to read metadata from Shapefiles but also from other types of geo files, such as KML. You will need another UDF for reading data from geo formats, including Shapefiles. To create one, run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOFILE(PATH_TO_FILE string, filename string)\nRETURNS TABLE (wkt string, properties object)\nLANGUAGE python\nRUNTIME_VERSION = 3.8\nPACKAGES = ('fiona', 'shapely', 'snowflake-snowpark-python')\nHANDLER = 'GeoFileReader'\nAS $$\n# Import necessary modules for geometry handling and file operations\nfrom shapely.geometry import shape\nfrom snowflake.snowpark.files import SnowflakeFile\nfrom fiona.io import ZipMemoryFile\nimport fiona\n\n# Define the handler class for the UDF\nclass GeoFileReader:\n    def process(self, PATH_TO_FILE: str, filename: str):\n        # Enable support for KML drivers in Fiona\n        fiona.drvsupport.supported_drivers['libkml'] = 'rw'\n        fiona.drvsupport.supported_drivers['LIBKML'] = 'rw'\n\n        # Open the file from the Snowflake stage in binary read mode\n        with SnowflakeFile.open(PATH_TO_FILE, 'rb') as f:\n            # Read the zip file into memory using Fiona's ZipMemoryFile\n            with ZipMemoryFile(f) as zip:\n                # Open the specified geospatial file within the zip archive\n                with zip.open(filename) as collection:\n                    # Iterate over each feature (record) in the collection\n                    for record in collection:\n                        # Check if the geometry is not None\n                        if record['geometry'] is not None:\n                            # Convert the geometry to Well-Known Text (WKT) format\n                            wkt = shape(record['geometry']).wkt\n                            # Convert the properties to a dictionary\n                            properties = dict(record['properties'])\n                            # Yield the WKT and properties as a tuple\n                            yield (wkt, properties)\n$$;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you can look into the metadata of \u003Ccode\u003EWorldClim.shp\u003C/code\u003E stored in \u003Ccode\u003EWorldClim.zip\u003C/code\u003E package:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT parse_json(metadata):crs as metadata\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOFILE_METADATA(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'WorldClim.zip'), 'WorldClim.shp'));\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIt stores spatial objects using the Spatial Reference System \u003Ccode\u003EEPSG:4326\u003C/code\u003E. You can examine the Shapefile to check its structure:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT top 10 *\nFROM table(PY_LOAD_GEOFILE(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'WorldClim.zip'), 'WorldClim.shp'));\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_24.png\" alt=\"assets/geo_ml_24.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EIt stores geo objects in the \u003Ccode\u003EWKT\u003C/code\u003E column and precipitation (\u003Ccode\u003EPREC\u003C/code\u003E) and average temperature (\u003Ccode\u003ETAVG\u003C/code\u003E) as properties in a JSON-like object. Knowing this information, it's easy to create a query that reads data from a Shapefile and stores it in a table. This is what you will do in the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.WORLDWIDE_WEATHER AS\nSELECT to_geography(wkt) as geog, \nproperties:&quot;PREC&quot;::float as prec, \nproperties:&quot;TAVG&quot;::float as tavg\nFROM table(PY_LOAD_GEOFILE(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'WorldClim.zip'), 'WorldClim.shp'));\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow that you have a table with average temperature and precipitation, you can visualize it using \u003Ccode\u003Epydeck\u003C/code\u003E. Since the newly created table \u003Ccode\u003EADVANCED_ANALYTICS.RASTER.WORLDWIDE_WEATHER\u003C/code\u003E contains more than 800K rows, you may want to reduce its size. In the Python code below, you will do the following:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ERead data from the weather table, group it using H3 cells of resolution 3, and calculate the average temperature value for each cell.\u003C/li\u003E\u003Cli\u003EGet the GeoJSON of H3 cell centroids and pass it to a Pandas DataFrame.\u003C/li\u003E\u003Cli\u003EExtract the longitude and latitude coordinates from the GeoJSON data to prepare it for visualization.\u003C/li\u003E\u003Cli\u003EDefine a color map and assign a color to each data point based on where its value falls within the quantiles.\u003C/li\u003E\u003Cli\u003EFinally, create a \u003Ccode\u003Epydeck\u003C/code\u003E scatterplot layer using the processed data.\nOf course, you could also visualize data using H3 cells as you've done before, but to demonstrate different visualization approaches, you will use points with a radius of 50 km. You can replace \u003Ccode\u003Eavg(tavg)\u003C/code\u003E with \u003Ccode\u003Eavg(prec)\u003C/code\u003E to visualize precipitation instead of average temperature.\u003C/li\u003E\u003C/ul\u003E\n\u003Cpre\u003E\u003Ccode\u003Eimport streamlit as st\nimport pandas as pd\nimport numpy as np\nimport pydeck as pdk\nimport json\nfrom typing import List\nimport branca.colormap as cm\nfrom snowflake.snowpark.context import get_active_session\n\nsession = get_active_session()\ndf = session.sql('''select st_asgeojson(h3_cell_to_point(h3_point_to_cell(geog, 3))) as geog, \n                    avg(tavg) as value from ADVANCED_ANALYTICS.RASTER.WORLDWIDE_WEATHER\n                    group by all;''').to_pandas()\n\ndf[&quot;lon&quot;] = df[&quot;GEOG&quot;].apply(lambda row: json.loads(row)[&quot;coordinates&quot;][0])\ndf[&quot;lat&quot;] = df[&quot;GEOG&quot;].apply(lambda row: json.loads(row)[&quot;coordinates&quot;][1])\n\n\ndf[&quot;VALUE&quot;] = df[&quot;VALUE&quot;].apply(lambda row: float(row))\ncenter_latitude = df['lat'].mean()\ncenter_longitude = df['lon'].mean()\n\ndef get_quantiles(df_column: pd.Series, quantiles: List) -&gt; pd.Series:\n    return df_column.quantile(quantiles)\n\ndef get_color(df_column: pd.Series, colors: List, vmin: int, vmax: int, index: pd.Series) -&gt; pd.Series:\n    color_map = cm.LinearColormap(colors, vmin=vmin, vmax=vmax, index=index)\n    return df_column.apply(color_map.rgb_bytes_tuple)\n\nquantiles = get_quantiles(df[&quot;VALUE&quot;], [0, 0.2, 0.4, 0.6, 0.8, 1])\ncolors = ['gray','blue','green','yellow','orange','red']\n\ndf['COLOR'] = get_color(df['VALUE'], colors, quantiles.min(), quantiles.max(), quantiles)\n\nst.pydeck_chart(pdk.Deck(\n    map_style=None,\n    initial_view_state=pdk.ViewState(\n        latitude=center_latitude,\n        longitude=center_longitude, pitch=0, zoom=0\n    ),\n    layers=[\n        pdk.Layer(\n            &quot;ScatterplotLayer&quot;,\n            data=df,\n            get_position=[&quot;lon&quot;, &quot;lat&quot;],\n            opacity=0.9,\n            stroked=True,\n            filled=True,\n            extruded=True,\n            wireframe=True,\n            get_color='COLOR',\n            get_fill_color='COLOR',\n            get_radius=&quot;50000&quot;,\n            auto_highlight=True,\n            pickable=False,\n        )\n    ],\n))\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_25.png\" alt=\"assets/geo_ml_25.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ENow that you have all the data from GeoTiff and Shapefile stored in Snowflake tables, you can join them.\u003C/p\u003E\n","\u003Ch3\u003EStep 4. Joining Data from Different Sources\u003C/h3\u003E\n","\u003Cp\u003EIn this step, you will join data from two datasets. Let's start by joining the \u003Ccode\u003EElevation\u003C/code\u003E and \u003Ccode\u003EWeather\u003C/code\u003E datasets. We observed in the visualizations above that the Elevation dataset covers a relatively small area in Africa, whereas the Weather dataset covers the whole world. To speed up joining these datasets, we can remove from the Weather dataset all points that are outside our area of interest, which corresponds to the coverage area of the Elevation dataset.\u003C/p\u003E\n","\u003Cp\u003EIn the query below, you will do the following:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EUse native \u003Ccode\u003EST_\u003C/code\u003E functions to get the minimum and maximum boundaries of the Elevation dataset and create a polygon that corresponds to its outer boundaries.\u003C/li\u003E\u003Cli\u003EUse the SedonaSnow \u003Ca href=\"https://sedona.apache.org/1.5.1/api/snowflake/vector-data/Function/#st_buffer\"\u003EST_Buffer\u003C/a\u003E function to extend that boundary by 0.5 degrees in all directions.\u003C/li\u003E\u003Cli\u003EFilter out from the Weather dataset all points that are outside the boundaries created in the previous step.\u003C/li\u003E\u003Cli\u003ECreate the \u003Ccode\u003EADVANCED_ANALYTICS.RASTER.WORLDWIDE_WEATHER\u003C/code\u003E table with the new results.\u003C/li\u003E\u003C/ul\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER AS\nwith boundary as (SELECT min(st_xmin(geog)) as xmin, \nmax(st_xmax(geog)) as xmax,\nmin(st_ymin(geog)) as ymin,\nmax(st_ymax(geog)) as ymax,\nsedonasnow.sedona.st_buffer(to_geography('POLYGON ((' || xmin || ' ' || ymin || ', ' ||\n       \t\t\t       xmin || ' ' || ymax || ', ' ||\n       \t\t\t       xmax || ' ' || ymax || ', ' ||\n       \t\t\t       xmax || ' ' || ymin || ', ' ||\n       \t\t\t       xmin || ' ' || ymin ||'))'), 0.5) as external_boundary\nFROM ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION)\nSELECT *\nFROM ADVANCED_ANALYTICS.RASTER.WORLDWIDE_WEATHER,\nboundary\nWHERE ST_INTERSECTS(external_boundary, geog)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow chech how many points contains the new dataset:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT COUNT(*) FROM ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EOne hundred forty. As a next step, you need to join the \u003Ccode\u003EAFRICA_ELEVATION\u003C/code\u003E table with \u003Ccode\u003EAFRICA_WEATHER\u003C/code\u003E. Our goal is to find, for each point in the elevation dataset, the closest point from the weather dataset to get weather information. We can do this using different approaches. One approach would be to calculate nearest neighbours using an \u003Ccode\u003EST_DWITHIN\u003C/code\u003E-based join. In the query below, you join two datasets using points that are within 200 km of each other, then partition by objects in \u003Ccode\u003EAFRICA_ELEVATION\u003C/code\u003E and, in each partition, sort by distance and keep only the first element:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.ELEVATION_WEATHER_NN AS\nSELECT t1.geog,\n       t1.band,\n       t2.prec,\n       t2.tavg\nFROM ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION t1,\n     ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER t2,\nWHERE ST_DWITHIN(t1.geog, t2.geog, 200000) QUALIFY ROW_NUMBER() OVER \n(PARTITION BY st_aswkb(t1.geog) ORDER BY ST_DISTANCE(t1.geog, t2.geog)) &lt;= 1;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIt took more than 5 minutes on a \u003Ccode\u003ELARGE\u003C/code\u003E warehouse. The problem with the query above is that, as a result of the join, it creates an internal table with 1.8 billion rows (12,967,201 &times; 140), which makes it quite a complex join.\u003C/p\u003E\n","\u003Cp\u003ELet's try another approach, which will include two steps:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EFor the \u003Ccode\u003EAFRICA_WEATHER\u003C/code\u003E table, create a table with Voronoi polygons.\u003C/li\u003E\u003Cli\u003EJoin \u003Ccode\u003EAFRICA_ELEVATION\u003C/code\u003E and \u003Ccode\u003EAFRICA_WEATHER\u003C/code\u003E using Voronoi polygons and \u003Ccode\u003EST_WITHIN\u003C/code\u003E instead of \u003Ccode\u003EST_DWITHIN\u003C/code\u003E.\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EA Voronoi polygon is essentially a region consisting of all points closer to a specific seed point than to any other seed point, effectively partitioning space into cells around each seed. So when you join \u003Ccode\u003EAFRICA_ELEVATION\u003C/code\u003E with \u003Ccode\u003EAFRICA_WEATHER\u003C/code\u003E using points from the elevation table and Voronoi polygons from the weather table, you can be sure that for each elevation point, you are associating it with its nearest weather data.\u003C/p\u003E\n","\u003Cp\u003ETo build Voronoi polygons, you will use the \u003Ca href=\"https://sedona.apache.org/1.5.1/api/snowflake/vector-data/Function/#st_voronoipolygons\"\u003EST_VORONOIPOLYGONS\u003C/a\u003E function from the \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZTYZF0RTY3/wherobots-ai-sedonasnow\"\u003ESedonaSnow\u003C/a\u003E native app. It takes a multi-object as input&mdash;in our case, a Multipoint&mdash;and returns Voronoi polygons for that object. Since it returns all polygons also as one object, we need a function that converts a multipolygon into multiple separate polygons. Run the following query to create such a UDF:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.ST_GETPOLYGONS(G OBJECT)\nRETURNS TABLE (POLYGON OBJECT)\nLANGUAGE JAVASCRIPT\nAS '\n{\nprocessRow: function split_multipolygon(row, rowWriter, context){\n    let geojson = row.G;\n    let polygons = [];\n    \n    function extractPolygons(geometry) {\n        if (geometry.type === &quot;Polygon&quot;) {\n            polygons.push(geometry.coordinates);\n        } else if (geometry.type === &quot;MultiPolygon&quot;) {\n            for (let i = 0; i &lt; geometry.coordinates.length; i++) {\n                polygons.push(geometry.coordinates[i]);\n            }\n        } else if (geometry.type === &quot;GeometryCollection&quot;) {\n            for (let i = 0; i &lt; geometry.geometries.length; i++) {\n                extractPolygons(geometry.geometries[i]);\n            }\n        }\n        // Ignore other geometry types (e.g., Point, LineString)\n    }\n    \n    extractPolygons(geojson);\n    \n    for (let i = 0; i &lt; polygons.length; i++) {\n        rowWriter.writeRow({POLYGON: {\n                &quot;type&quot; : &quot;Polygon&quot;,\n                &quot;coordinates&quot;: polygons[i]\n            }\n        });\n    }\n}\n}\n';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIn the next query, you build Voronoi polygons for the weather table and enrich the \u003Ccode\u003EAFRICA_WEATHER\u003C/code\u003E table with those polygons:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER AS\n-- voronoi_grid CTE that stores all points from AFRICA_WEATHER table \n-- into one object and creates Voronoi Polygons\nwith voronoi_grid as (SELECT sedonasnow.sedona.ST_VoronoiPolygons(st_union_agg(geog)) as polygons\nfrom ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER),\n-- CTE that flattens results of voronoi_grid CTE\nvoronoi_grid_flattened as (select to_geography(polygon) as polygon\nfrom voronoi_grid,\ntable(ADVANCED_ANALYTICS.RASTER.ST_GETPOLYGONS(st_asgeojson(polygons))))\n-- Below you join table with voronoi polygons and table with weather information\nSELECT *\nFROM ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER\nINNER JOIN voronoi_grid_flattened\nON ST_WITHIN(geog, polygon);\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow when you have voronoi polygons in \u003Ccode\u003EAFRICA_WEATHER\u003C/code\u003E table, you can join \u003Ccode\u003EAFRICA_ELEVATION\u003C/code\u003E and \u003Ccode\u003EAFRICA_WEATHER\u003C/code\u003E. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.ELEVATION_WEATHER_VORONOI AS\nselect t1.geog, band, prec, tavg\nfrom ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION t1\nINNER JOIN ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER t2\nON ST_INTERSECTS(t1.geog, t2.polygon)\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ELet's look inside of the newly created table:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT TOP 5 * FROM ADVANCED_ANALYTICS.RASTER.ELEVATION_WEATHER_VORONOI\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_26.png\" alt=\"assets/geo_ml_26.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ENow you have data from two unstructured sources stored in a single table. You can use this table to feed into an ML model or enrich it further with some additional features.\u003C/p\u003E\n","\u003Ch3\u003EAdvanced Raster Use Case\u003C/h3\u003E\n","\u003Cp\u003ESometimes raster files can be really large, but as we mentioned earlier, often most of the points contain no data or some default values. As an example, let's look at the raster file from \u003Ca href=\"https://skogsdatalabbet.se/services/\"\u003EForrest Data Lab\u003C/a\u003E (\u003Ccode\u003ESkogsdatalabbets filserver vid SLU\u003C/code\u003E &rsaquo; \u003Ccode\u003ESLU_Forest_Map\u003C/code\u003E &rsaquo; \u003Ccode\u003ETradslag\u003C/code\u003E). \u003Ccode\u003EBok_andel.tif\u003C/code\u003E is 149 MB in size and has a resolution of 52,600&times;123,200, which results in about 6.5 billion points. Loading all those points would be quite an expensive step, but let's check how band values are distributed inside of that file. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT *\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_RASTER_BAND_VALUE_STATS(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'Bok_andel.tif')));\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EYou see that the most frequent band value is 0, which corresponds to 99% of the points. If we load data without those points, we probably won't lose any useful information, but we can have a good saving on compute. Additionally, you can resample the raster to reduce its size. Create a UDF that does both - it resamples to reduce the initial file to the given number of points (50M by default) and ignores given band values:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOTIFF_RESAMPLE_SKIP(\n    PATH_TO_FILE STRING,\n    SKIP_VALUES ARRAY DEFAULT NULL,   -- Optional SKIP_VALUES parameter\n    MAX_PIXELS INT DEFAULT 50000000   -- New optional MAX_PIXELS parameter with default value\n)\nRETURNS TABLE (\n    x FLOAT,\n    y FLOAT,\n    band_values ARRAY,\n    band_count INT\n)\nLANGUAGE PYTHON\nRUNTIME_VERSION = '3.8'\nPACKAGES = ('numpy', 'rasterio', 'snowflake-snowpark-python')\nHANDLER = 'GeoTiffExtractor'\nAS $$\nimport numpy as np\nimport rasterio\nfrom rasterio.enums import Resampling\nfrom snowflake.snowpark.files import SnowflakeFile\nimport math\n\nclass GeoTiffExtractor:\n    def process(self, PATH_TO_FILE: str, SKIP_VALUES=None, MAX_PIXELS=500000000):\n        try:\n            # Read the GeoTIFF file from the specified stage path\n            with SnowflakeFile.open(PATH_TO_FILE, 'rb', require_scoped_url=False) as input_file:\n                tif_bytes = input_file.read()  # Read the entire file into bytes\n\n            # Use rasterio's MemoryFile to read the TIFF data from memory\n            with rasterio.MemoryFile(tif_bytes) as memfile:\n                with memfile.open() as dataset:\n                    # Get the original dimensions\n                    height = dataset.height\n                    width = dataset.width\n\n                    total_pixels = height * width\n\n                    if total_pixels &gt; MAX_PIXELS:\n                        # Calculate scaling factor\n                        scaling_factor = math.sqrt(MAX_PIXELS / total_pixels)\n                        new_height = int(height * scaling_factor)\n                        new_width = int(width * scaling_factor)\n\n                        # Read the data with the new dimensions\n                        data = dataset.read(\n                            out_shape=(\n                                dataset.count,\n                                new_height,\n                                new_width\n                            ),\n                            resampling=Resampling.average\n                        )\n\n                        # Update the transform for the new dimensions\n                        transform = dataset.transform * dataset.transform.scale(\n                            (width / new_width),\n                            (height / new_height)\n                        )\n                    else:\n                        # Read all bands into a NumPy array\n                        data = dataset.read()  # Shape: (band_count, rows, cols)\n                        transform = dataset.transform\n                        new_height = height\n                        new_width = width\n\n                    # Get the number of bands\n                    band_count = data.shape[0]\n\n                    # Get the coordinates\n                    rows, cols = np.indices((new_height, new_width))\n                    xs, ys = rasterio.transform.xy(\n                        transform, rows, cols, offset='center'\n                    )\n\n                    # Flatten the arrays\n                    xs = np.array(xs).flatten()\n                    ys = np.array(ys).flatten()\n                    pixel_values = data.reshape((band_count, -1)).T  # Shape: (num_pixels, band_count)\n\n                    # Handle SKIP_VALUES\n                    if SKIP_VALUES:\n                        # Convert SKIP_VALUES to a NumPy array for efficient comparison\n                        skip_values = np.array(SKIP_VALUES)\n\n                        # Create a mask for pixels to skip\n                        skip_mask = np.isin(pixel_values, skip_values).any(axis=1)\n                        # Invert the skip_mask to get the mask of pixels to keep\n                        mask = ~skip_mask\n\n                        # Apply the mask to xs, ys, and pixel_values\n                        xs_filtered = xs[mask]\n                        ys_filtered = ys[mask]\n                        pixel_values_filtered = pixel_values[mask]\n                    else:\n                        # If SKIP_VALUES not provided, use all data\n                        xs_filtered = xs\n                        ys_filtered = ys\n                        pixel_values_filtered = pixel_values\n\n                    # For each pixel, yield a row with x, y, and band values\n                    for i in range(len(xs_filtered)):\n                        # Get the pixel values for all bands\n                        band_vals = pixel_values_filtered[i].tolist()\n                        yield (\n                            xs_filtered[i],\n                            ys_filtered[i],\n                            band_vals,\n                            band_count\n                        )\n        except Exception as e:\n            raise Exception(f&quot;Error during data extraction: {e}&quot;)\n$$;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003ENow you can load data from \u003Ccode\u003EBok_andel.tif\u003C/code\u003E. Run the query below to reduce the size of the initial file to 500 million points and ignore points where the band value equals zero.\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.BOK_ANDEL AS\nSELECT x, y,\nband_values[0]::float as band\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOTIFF_RESAMPLE_SKIP(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'Bok_andel.tif'), [0], 500000000));\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIn the prevous query you stored \u003Ccode\u003Ex\u003C/code\u003E and \u003Ccode\u003Ey\u003C/code\u003E as raw coordinates and the size of the newly created table has 7,028,074 rows. In the following query you check the metadata of the initial file to see what SRID it uses:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT *\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_EXTRACT_GEOTIFF_METADATA(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'Bok_andel.tif')));\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThe SRID is \u003Ccode\u003EEPSG:25833\u003C/code\u003E. To store data as \u003Ccode\u003EGEOGRAPHY\u003C/code\u003E type for further visualisation you need to convert it into \u003Ccode\u003EEPSG:4326\u003C/code\u003E. Run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ECREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.BOK_ANDEL AS\nSELECT TO_GEOGRAPHY(ST_TRANSFORM(ST_MAKEGEOMPOINT(x, y), 25833, 4326)) as geom, band\nFROM ADVANCED_ANALYTICS.RASTER.BOK_ANDEL\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EAs a final step you visualize the results using H3 cells:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Eimport streamlit as st\nimport pandas as pd\nimport pydeck as pdk\nfrom typing import List\nimport branca.colormap as cm\nfrom snowflake.snowpark.context import get_active_session\n\nsession = get_active_session()\n\n# Execute the updated SQL query\ndf = session.sql('''select h3_point_to_cell_string(geom, 7) as h3_cell,\n                    st_x(h3_cell_to_point(h3_cell)) as lon,\n                    st_y(h3_cell_to_point(h3_cell)) as lat,\n                    avg(band) as band \n                    FROM ADVANCED_ANALYTICS.RASTER.BOK_ANDEL\n                    group by all;''').to_pandas()\n\ndf[&quot;BAND&quot;] = df[&quot;BAND&quot;].apply(lambda row: float(row))\ncenter_latitude = df['LAT'].mean()\ncenter_longitude = df['LON'].mean()\n\ndef get_quantiles(df_column: pd.Series, quantiles: List) -&gt; pd.Series:\n    return df_column.quantile(quantiles)\n\ndef get_color(df_column: pd.Series, colors: List, vmin: int, vmax: int, index: pd.Series) -&gt; pd.Series:\n    color_map = cm.LinearColormap(colors, vmin=vmin, vmax=vmax, index=index)\n    return df_column.apply(color_map.rgb_bytes_tuple)\n    \nquantiles = get_quantiles(df[&quot;BAND&quot;], [0, 0.2, 0.4, 0.6, 0.8, 1])\ncolors = ['palegreen', 'lightgreen', 'mediumseagreen', 'forestgreen', 'seagreen', 'darkgreen']\n\ndf['BAND'] = get_color(df['BAND'], colors, quantiles.min(), quantiles.max(), quantiles)\n\nst.pydeck_chart(pdk.Deck(\n    map_style=None,\n    initial_view_state=pdk.ViewState(\n        latitude=center_latitude,\n        longitude=center_longitude, \n        zoom=5.2, \n        bearing=0, \n        pitch=0),\n    layers=[\n        pdk.Layer(\n            &quot;H3HexagonLayer&quot;,\n            df,\n            opacity=0.9,\n            stroked=False,\n            get_hexagon=&quot;H3_CELL&quot;,\n            get_fill_color='BAND',\n            extruded=False,\n            wireframe=True,\n            line_width_min_pixels=0,\n            auto_highlight=True,\n            pickable=False,\n            filled=True\n        )\n    ],\n))\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_27.png\" alt=\"assets/geo_ml_27.png\"\u003E\u003C/p\u003E\n","\u003Ch3\u003EConclusion\u003C/h3\u003E\n","\u003Cp\u003EIn this lab, you have learned how to load geospatial data from unstructured formats, such as GeoTiff and Shapefiles and what techniques you can apply when you need to join data using nearest neighbout approach. You can use these or similar UDFs to load data from other formats.\u003C/p\u003E\n","\u003Ch2\u003ECreating Interactive Maps with Kepler.gl\u003C/h2\u003E\n","\u003Cp\u003EIn this Lab you will learn how to create interactive maps directly within Snowflake using \u003Ca href=\"https://kepler.gl\"\u003EKepler.gl\u003C/a\u003E, powered by \u003Ca href=\"https://dekart.xyz/docs/snowflake-snowpark/about/\"\u003EDekart.xyz\u003C/a\u003E. You will use Dekart.XYZ app and use public datasets from Marketplace to visualize UK highways with color-coded density of nearby EV charging stations.\u003C/p\u003E\n","\u003Cp\u003EYor final result will be a map similar to this one:\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_39.png\" alt=\"assets/geo_ml_39.png\"\u003E\u003C/p\u003E\n","\u003Ch3\u003EData aquisition\u003C/h3\u003E\n","\u003Cp\u003EFor this project you will use an Overture Maps \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9M9/carto-overture-maps-divisions\"\u003EDivisions\u003C/a\u003E, \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9KR/carto-overture-maps-places\"\u003EPlaces\u003C/a\u003E, and \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9KJ/carto-overture-maps-transportation\"\u003ETransportation\u003C/a\u003E datasets offered by CARTO as free Marketplace listins.\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ENavigate to the Marketplace screen using the menu on the left side of the window\u003C/li\u003E\u003Cli\u003ESearch for \u003Ccode\u003EOverture Maps - Divisions\u003C/code\u003E in the search bar\u003C/li\u003E\u003Cli\u003EOnce in the listing, click the big blue \u003Ccode\u003EGet\u003C/code\u003E button\u003C/li\u003E\u003C/ul\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EOn the \u003Ccode\u003EGet\u003C/code\u003E screen, you may be prompted to complete your \u003Ccode\u003Euser profile\u003C/code\u003E if you have not done so before. Click the link as shown in the screenshot below. Enter your name and email address into the profile screen and click the blue \u003Ccode\u003ESave\u003C/code\u003E button. You will be returned to the \u003Ccode\u003EGet\u003C/code\u003E screen.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_28.png\" alt=\"assets/geo_ml_28.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ESimilarly you need to find and install \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9KR/carto-overture-maps-places\"\u003EOverture Maps - Places\u003C/a\u003E, and \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9KJ/carto-overture-maps-transportation\"\u003EOverture Maps - Transportation\u003C/a\u003E datasets.\u003C/p\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003EThese datasets include information on administrative divisions, transportation routes, and points of interest. The \u003Ca href=\"https://docs.overturemaps.org/schema/reference/\"\u003EOverture Maps Schema Reference\u003C/a\u003E is an excellent resource to understand the structure and details of each dataset.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Ch3\u003EInstalling Dekart.xyz\u003C/h3\u003E\n","\u003Cp\u003EIn this step you will install \u003Ca href=\"https://app.snowflake.com/marketplace/listing/GZSYZJNO4W/dekart-xyz-dekart-%E2%80%93-kepler-gl-maps-inside-snowflake\"\u003EDekart &ndash; Kepler.gl maps inside Snowflake\u003C/a\u003E application and run it inside of Snowpark Container Services.\u003C/p\u003E\n","\u003Cp\u003EAs a first step you will install the Marketplace listing:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003ENavigate to the Marketplace screen using the menu on the left side of the window\u003C/li\u003E\u003Cli\u003ESearch for \u003Ccode\u003EDekart &ndash; Kepler.gl maps inside Snowflake\u003C/code\u003E in the search bar\u003C/li\u003E\u003Cli\u003EOnce in the listing, click the big blue \u003Ccode\u003EGet\u003C/code\u003E button\u003C/li\u003E\u003Cli\u003EIn the &quot;Warehouse used for installation&quot; field select the warehouse which will be used for installation process.\u003C/li\u003E\u003Cli\u003EClick the \u003Ccode\u003ETry for Free\u003C/code\u003E button\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_29.png\" alt=\"assets/geo_ml_29.png\"\u003E\u003C/p\u003E\n\u003Cblockquote\u003E\n","\u003Cp\u003ENote: When trial end you won't be automatically swithched to Subscription-based usage. If you decide to continue using Dekart, you would need to manually enable subscription.\u003C/p\u003E\n\u003C/blockquote\u003E\n","\u003Cp\u003EFollow the installation instructions as displayed in the Snowsight interface.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_30.png\" alt=\"assets/geo_ml_30.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EGrant Account Privileges to Dekart and allow connections to the Mapbox API. Dekart uses Mapbox for rendering maps. No user data is sent to Mapbox. Dekart creates a single node \u003Ccode\u003ECPU_X64_XS\u003C/code\u003E compute pool and \u003Ccode\u003EXSMALL\u003C/code\u003E warehouse.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_31.png\" alt=\"assets/geo_ml_31.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EClick \u003Ccode\u003EActivate\u003C/code\u003E. Activation process might take up to 10 minutes.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_32.png\" alt=\"assets/geo_ml_32.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EWhile it's activating you can go to Worksheets and grant access to Overture Maps datasets. This ensures that Dekart can read and visualize the data within Snowflake. Note, tht since you run Dekart withon Snowflake Container Services, your data stays in Snowflake and won't be transfered externally. Execute the following SQL commands in Snowflake (make sure you have the \u003Ccode\u003EACCOUNTADMIN\u003C/code\u003E role for these operations):\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EGRANT IMPORTED PRIVILEGES ON DATABASE OVERTURE_MAPS__TRANSPORTATION TO application DEKART__KEPLER_GL_MAPS_INSIDE_SNOWFLAKE;\nGRANT IMPORTED PRIVILEGES ON DATABASE OVERTURE_MAPS__DIVISIONS TO application DEKART__KEPLER_GL_MAPS_INSIDE_SNOWFLAKE;\nGRANT IMPORTED PRIVILEGES ON DATABASE OVERTURE_MAPS__PLACES TO application DEKART__KEPLER_GL_MAPS_INSIDE_SNOWFLAKE;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EWhen Activation is done, do the following steps:\u003C/p\u003E\n\u003Cul\u003E\u003Cli\u003EOpen the Dekart App within Snowsight by going to \u003Ccode\u003EData Products\u003C/code\u003E &gt; \u003Ccode\u003EApps\u003C/code\u003E. Selecting Dekart app and click \u003Ccode\u003ELaunch App\u003C/code\u003E.\u003C/li\u003E\u003Cli\u003EAuthorize the Dekart App with your Snowflake account.\u003C/li\u003E\u003Cli\u003EIn the Dekart interface, click \u003Ccode\u003ECreate Report\u003C/code\u003E to start building your map.\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_33.png\" alt=\"assets/geo_ml_33.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003ECongratulations! You have now Dekart app running in your Snowflake environment and now you're ready to start creating maps!\u003C/p\u003E\n","\u003Ch3\u003EBuild maps with SQL in Dekart\u003C/h3\u003E\n","\u003Cp\u003EDekart allows you to visualize data directly from SQL queries, which means you can write custom queries to shape the data as you like.\u003C/p\u003E\n","\u003Cp\u003EIn the new report screen you see three main components: the \u003Ccode\u003ESQL\u003C/code\u003E panel on the right, the \u003Ccode\u003ELayers\u003C/code\u003E panel on the left and the map in the center. Rename the report, set the name to \u003Ccode\u003ECharging Station Density\u003C/code\u003E. Rename the first SQL tab to \u003Ccode\u003Euk_boundary\u003C/code\u003E and run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003ESELECT ST_ASWKT(GEOMETRY) as GEOMETRY\nFROM OVERTURE_MAPS__DIVISIONS.CARTO.DIVISION_AREA\nWHERE COUNTRY = 'GB' AND SUBTYPE = 'country';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIn this query you use Overture Maps - Divisions dataset to get the shape of the UK boundary. As soon as query is completed, you will see a new layer in the \u003Ccode\u003ELayers\u003C/code\u003E panel. You can expand it, to customise if needed, for example to make it transparent you can turn off \u003Ccode\u003EFill color\u003C/code\u003E toggle.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_35.gif\" alt=\"assets/geo_ml_35.gif\"\u003E\u003C/p\u003E\n","\u003Cp\u003EAs a next step, add a road network for the UK. Create a new tab in \u003Ccode\u003ESQL\u003C/code\u003E panel and name it \u003Ccode\u003Euk_roads\u003C/code\u003E. Run the following query that joins road data from \u003Ccode\u003EOverture Maps - Transportation\u003C/code\u003E dataset and filters it so it shows only motoways and trunk roads for the UK area:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003Ewith uk_boundary as (SELECT GEOMETRY\nFROM OVERTURE_MAPS__DIVISIONS.CARTO.DIVISION_AREA\nWHERE COUNTRY = 'GB' AND SUBTYPE = 'country')\nSELECT ST_ASWKT(s.GEOMETRY) as GEOMETRY, s.NAMES, s.ID\nFROM OVERTURE_MAPS__TRANSPORTATION.CARTO.SEGMENT s, uk_boundary ub\nWHERE ST_INTERSECTS(ub.GEOMETRY, s.GEOMETRY) AND s.CLASS IN ('motorway', 'trunk');\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EWhen the query is complete, you'll see the new layer in the Layers panel with the name \u003Ccode\u003Euk_roads\u003C/code\u003E and it contains about 126K road segments that are viualised on the map. You can change the colour of the linestrings using \u003Ccode\u003EStroke Color\u003C/code\u003E field in the corresponding Layer.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_36.png\" alt=\"assets/geo_ml_36.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EIn the next step you will add locations of Electric Vehicles charging sttions as a new layer. Create a new SQL tab, name it \u003Ccode\u003EEV_stations\u003C/code\u003E and run the following query:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EWITH uk_boundary AS (SELECT GEOMETRY\nFROM OVERTURE_MAPS__DIVISIONS.CARTO.DIVISION_AREA\nWHERE COUNTRY = 'GB' AND SUBTYPE = 'country')\nSELECT ST_ASWKT(p.GEOMETRY) as GEOMETRY\nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE p, uk_boundary ub\nWHERE ST_CONTAINS(ub.GEOMETRY, p.GEOMETRY) AND p.CATEGORIES::TEXT ILIKE '%charging%';\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EIn the newly created layer \u003Ccode\u003EEV_stations\u003C/code\u003E play with \u003Ccode\u003EStroke Color\u003C/code\u003E and \u003Ccode\u003ERadius\u003C/code\u003E to adjust the size of points that correspond to chargins stations locations.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_37.png\" alt=\"assets/geo_ml_37.png\"\u003E\u003C/p\u003E\n","\u003Cp\u003EAs a last step, create a new SQL tab, name it \u003Ccode\u003EEV_stations_density\u003C/code\u003E and run the following query that for each road segment calculates number of charging stations within 50km radius:\u003C/p\u003E\n\u003Cpre\u003E\u003Ccode\u003EWITH uk_boundary as (SELECT GEOMETRY\nFROM OVERTURE_MAPS__DIVISIONS.CARTO.DIVISION_AREA\nWHERE COUNTRY = 'GB' AND SUBTYPE = 'country'),\nroad_segments as (SELECT s.GEOMETRY, s.NAMES, s.ID\nFROM OVERTURE_MAPS__TRANSPORTATION.CARTO.SEGMENT s, uk_boundary ub\nWHERE ST_INTERSECTS(ub.GEOMETRY, s.GEOMETRY) AND s.CLASS IN ('motorway', 'trunk')),\ncharging_stations as (SELECT p.GEOMETRY\nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE p, uk_boundary ub\nWHERE ST_CONTAINS(ub.GEOMETRY, p.GEOMETRY) AND p.CATEGORIES::TEXT ILIKE '%charging%'),\ncharging_count AS (\n   SELECT r.ID AS road_id, r.NAMES AS road_name, COUNT(cs.GEOMETRY) AS num_charging_stations\n   FROM road_segments r\n   LEFT JOIN charging_stations cs ON ST_DISTANCE(r.GEOMETRY, cs.GEOMETRY) &lt;= 50000\n   GROUP BY r.ID, r.NAMES\n)\nSELECT r.ID, r.NAMES, ST_ASWKT(r.GEOMETRY) as GEOMETRY, cc.num_charging_stations\nFROM road_segments r\nJOIN charging_count cc ON r.ID = cc.road_id;\n\u003C/code\u003E\u003C/pre\u003E\n","\u003Cp\u003EThis is our final visualization. Before editing its look and feel, you can hide other layers by clicking on the 'eye' icon. Then, select \u003Ccode\u003EEV_stations_density\u003C/code\u003E, click on the three dots next to the \u003Ccode\u003EStroke Color\u003C/code\u003E field, and choose \u003Ccode\u003ENUM_CHARGING_STATIONS\u003C/code\u003E as the source for the stroke color. You can also change the color map and select a color scheme of your choice.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_38.gif\" alt=\"assets/geo_ml_38.gif\"\u003E\u003C/p\u003E\n","\u003Cp\u003EYou can now use top right menu to save the newly created map and to share it within your organization.\u003C/p\u003E\n","\u003Ch3\u003EConclusion\u003C/h3\u003E\n","\u003Cp\u003EIn this Lab, you created an interactive, real-time map within Snowflake, using Dekart and the Overture Maps datasets. You explored UK highway infrastructure with a focus on EV charging station density.\u003C/p\u003E\n","\u003Cp\u003E\u003Cimg src=\"https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_39.png\" alt=\"assets/geo_ml_39.png\"\u003E\u003C/p\u003E\n","\u003Ch4\u003EWhat You Learned\u003C/h4\u003E\n\u003Cul\u003E\u003Cli\u003ECreating interactive maps directly within Snowflake using Kepler.gl and Dekart.\u003C/li\u003E\u003Cli\u003EAccessing and using public Overture Maps data to create meaningful geospatial visualizations.\u003C/li\u003E\u003Cli\u003EWriting SQL queries for filtering, calculating, and mapping geospatial data.\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch4\u003EResources\u003C/h4\u003E\n\u003Cul\u003E\u003Cli\u003E\u003Ca href=\"https://dekart.xyz/docs/about/snowflake-kepler-gl-examples/\"\u003ESnowflake Kepler.gl Maps Examples\u003C/a\u003E: Explore more examples and use cases for Kepler.gl in Snowflake.\u003C/li\u003E\u003Cli\u003E\u003Ca href=\"https://dekart.xyz/docs/snowflake-snowpark/about/\"\u003EDekart Snowpark Application Documentation\u003C/a\u003E: Learn more about Dekart and its capabilities.\u003C/li\u003E\u003Cli\u003E\u003Ca href=\"https://docs.overturemaps.org/schema/reference/\"\u003EOverture Maps Schema Reference\u003C/a\u003E: For more details on available tables and fields.\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch2\u003EConclusion And Resources\u003C/h2\u003E\n","\u003Cp\u003ECongratulations! You've successfully performed data analytics, data engineering and data science tasks for various use cases.\nCongratulations! You've successfully performed data analytics, data engineering and data science tasks for various use cases.\u003C/p\u003E\n","\u003Ch3\u003EWhat You Learned\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003EHow to acquire data from the Snowflake Marketplace\u003C/li\u003E\u003Cli\u003EHow to load data from external storage\u003C/li\u003E\u003Cli\u003EHow to transform geospatial data using H3 and Time Series functions\u003C/li\u003E\u003Cli\u003EHow to train models and predict results with Cortex ML\u003C/li\u003E\u003Cli\u003EHow to use LLM for analysing textual data\u003C/li\u003E\u003Cli\u003EHow to visualize data with Streamlit\u003C/li\u003E\u003C/ul\u003E\n","\u003Ch3\u003ERelated Resources\u003C/h3\u003E\n\u003Cul\u003E\u003Cli\u003E\u003Ca href=\"/en/developers/guides/geospatial-analytics-with-snowflake-and-carto-ny/\"\u003EGeospatial Analytics for Retail with Snowflake and CARTO\u003C/a\u003E\u003C/li\u003E\u003Cli\u003E\u003Ca href=\"/en/developers/guides/geo-analysis-geometry/\"\u003EGeospatial Analysis using Geometry and Geography Data Types quickstart\u003C/a\u003E\u003C/li\u003E\u003Cli\u003E\u003Ca href=\"/en/developers/guides/geo-performance/\"\u003EPerformance Optimization Techniques for Geospatial queries\u003C/a\u003E\u003C/li\u003E\u003C/ul\u003E\n","\u003Cp\u003EWe would love your feedback on this QuickStart Guide! Please submit your feedback using this \u003Ca href=\"https://forms.gle/tGDzTpu41huWFDXi9\"\u003EFeedback Form\u003C/a\u003E.\u003C/p\u003E\n","\u003Cp\u003EWe would love your feedback on this QuickStart Guide! Please submit your feedback using this \u003Ca href=\"https://forms.gle/tGDzTpu41huWFDXi9\"\u003EFeedback Form\u003C/a\u003E.\u003C/p\u003E"],"description":"Apply geospatial features to ML models with Streamlit and Snowflake for location-aware predictions, proximity analysis, and spatial clustering.","title":"Geospatial Analytics, AI and ML using Snowflake","isDeveloperGuidesPage":false,":type":"snowflake-site/components/contentfragment",":items":{},":itemsOrder":[],"elements":{"quickstartArticleBody":{"dataType":"string","title":"Quickstart Article Body","value":"\u003C!-- ----------------------------------------- --\u003E\n## Overview \n\n\nSnowflake offers a rich toolkit for predictive analytics with a geospatial component. It includes two data types and specialized functions for transformation, prediction, and visualization. This guide is divided into multiple labs, each covering a separate use case that showcases different features for a real-world scenario.\n\n### Prerequisites\n* Understanding of [Discrete Global Grid H3](/blog/getting-started-with-h3-hexagonal-grid/)\n* Recommend: Understanding of [Geospatial Data Types](https://docs.snowflake.com/en/sql-reference/data-types-geospatial) and [Geospatial Functions](https://docs.snowflake.com/en/sql-reference/functions-geospatial) in Snowflake\n* Recommended: Complete [Geospatial Analysis using Geometry and Geography Data Types](/en/developers/guides/geo-analysis-geometry/) quickstart\n* Recommended: Complete [Performance Optimization Techniques for Geospatial queries](/en/developers/guides/geo-performance/) quickstart\n\n### What You’ll Learn\nIn this quickstart, you will use H3, Time Series, Cortex ML and Streamlit for ML use cases. The quickstart is broken up into separate labs:\n* Lab 1: Geospatial 101\n* Lab 2: Energy grids analysis using GEOMETRY\n* Lab 3: Geocoding and Reverse Geocoding\n* Lab 4: Forecasting time series on a map\n* Lab 5: Sentiment analysis of customer reviews\n* Lab 6: Processing unstructured geospatial data\n* Lab 7: Creating Interactive Maps with Kepler.gl\n\nWhen you complete this quickstart, you will have gained practical experience in several areas:\n* Acquiring data from the Snowflake Marketplace\n* Loading data from external storage\n* Transforming data using H3 and Time Series functions\n* Training models and predicting results with Cortex ML\n* Using LLM for analysing textual data\n* Visualizing data with Streamlit\n* Processing unstructured geospaial data (GeoTIFF, Shapefiles)\n* Using geo visualisation apps available in Marketplace\n* Processing unstructured geospaial data (GeoTIFF, Shapefiles)\n* Using geo visualisation apps available in Marketplace\n\n### What You’ll Need\n* A supported Snowflake [Browser](https://docs.snowflake.com/en/user-guide/setup.html)\n* Sign-up for a [Snowflake Trial](https://signup.snowflake.com/?utm_source=snowflake-devrel&utm_medium=developer-guides&utm_cta=developer-guides) OR have access to an existing Snowflake account with the `ACCOUNTADMIN` role or the `IMPORT SHARE `privilege. Select the Enterprise edition, AWS as a cloud provider and US East (Northern Virginia) or EU (Frankfurt) as a region.\n\n\u003C!-- ----------------------------------------- --\u003E\n\n## Setup your Account\n\n\nIf this is the first time you are logging into the Snowflake UI, you will be prompted to enter your account name or account URL that you were given when you acquired a trial. The account URL contains your [account name](https://docs.snowflake.com/en/user-guide/connecting.html#your-snowflake-account-name) and potentially the region. You can find your account URL in the email that was sent to you after you signed up for the trial.\n\nClick `Sign-in` and you will be prompted for your username and password.\n\n\n\u003E  If this is not the first time you are logging into the Snowflake UI, you should see a \"Select an account to sign into\" prompt and a button for your account name listed below it. Click the account you wish to access and you will be prompted for your user name and password (or another authentication mechanism).\n\n### Increase Your Account Permission\nThe Snowflake web interface has a lot to offer, but for now, switch your current role from the default `SYSADMIN` to `ACCOUNTADMIN`. This increase in permissions will allow you to create shared databases from Snowflake Marketplace listings.\n\n\n\u003E  If you don't have the `ACCOUNTADMIN` role, switch to a role with `IMPORT SHARE` privileges instead.\n\n![assets/geo_ml_1.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_1.png)\n\n### Create a Virtual Warehouse\n\nYou will need to create a Virtual Warehouse to run queries.\n\n* Navigate to the `Admin \u003E Warehouses` screen using the menu on the left side of the window\n* Click the big blue `+ Warehouse` button in the upper right of the window\n* Create a LARGE Warehouse as shown in the screen below\n\n![assets/geo_ml_2.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_2.png)\n\nBe sure to change the `Suspend After (min)` field to 5 min to avoid wasting compute credits.\n\nNavigate to the query editor by clicking on `Worksheets` on the top left navigation bar and choose your warehouse.\n\n- Click the + Worksheet button in the upper right of your browser window. This will open a new window.\n- In the new Window, make sure `ACCOUNTADMIN` and `MY_WH` (or whatever your warehouse is named) are selected in the upper right of your browser window.\n\n![assets/geo_ml_3.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_3.png)\n\nCreate a new database and schema where you will store datasets in the `GEOGRAPHY` data type. Copy & paste the SQL below into your worksheet editor, put your cursor somewhere in the text of the query you want to run (usually the beginning or end), and either click the blue \"Play\" button in the upper right of your browser window, or press `CTRL+Enter` or `CMD+Enter` (Windows or Mac) to run the query.\n\n```\nCREATE DATABASE IF NOT EXISTS advanced_analytics;\n// Set the working database schema\nUSE ADVANCED_ANALYTICS.PUBLIC;\nUSE WAREHOUSE my_wh;\nALTER SESSION SET GEOGRAPHY_OUTPUT_FORMAT='WKT';\n```\n\n## Geospatial 101\n\n\n\u003E \n\u003E  Before starting with this lab, complete the preparation steps from `Setup your account` page.\n\n\u003E \n\u003E  This lab is [available](https://github.com/Snowflake-Labs/sf-guide-geospatial-analytics-ai-ml) as Snowflake Notebook.\n\n### 1. Overview\nGeospatial query capabilities in Snowflake are built upon a combination of data types and specialized query functions that can be used to parse, construct, and perform calculations on geospatial objects. Additionally, geospatial data can be visualized in Snowflake using Streamlit. This guide provides an entry-level introduction to geospatial analytics and visualization in Snowflake. In this lab, you will explore a sample use case of identifying the closest healthcare facilities near a geographic point, and you will learn:\n- How to view the GEOGRAPHY data type with supported formats\n- How to construct a geospatial object from latitude and longitude values\n- How to extract latitude and longitude from a geography column\n- How to perform geospatial calculations and filtering\n- How to visualize geospatial data using Streamlit in Snowflake\n\n\n### 2. Acquire Data\nFor this lab oyu will use [Overture Maps - Places](https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9KR/carto-overture-maps-places) dataset from Marketplace. Now you can acquire sample geospatial data from the Snowflake Marketplace.\n\n* Navigate to the Marketplace screen using the menu on the left side of the window\n* Search for `Overture Maps` in the search bar\n* Find and click the `Overture Maps - Places` tile\n\nOn the Get Data screen, keep the default database name OVERTURE_MAPS__PLACES, as all of the future instructions will assume this name for the database.\n\n\u003E \n\u003E  On the `Get` screen, you may be prompted to complete your `user profile` if you have not done so before. Click the link as shown in the screenshot below. Enter your name and email address into the profile screen and click the blue `Save` button. You will be returned to the `Get` screen.\n\n\nCongratulations! You have just created a shared database from a listing on the Snowflake Marketplace.\n\nAs one additional preparation step you need to complete is to import libraries that you will use in this Lab, navigate to the `Packages` drop-down  in the upper right of the Notebook and search for `pydeck`. Click on `pydeck` to add it to the Python packages.\n\n### 3. Understanding Snowflake Geospatial Formats\nSnowflake supports GeoJSON, Well-Known Text (WKT) and Well-Known Binary (WKB) formats for loading and unloading geospatial data. You can use session or account parameters to control which of these format types is used to display geospatial data in your query results.\n\nRun the query below to explicitly set your geography output format to JSON.\n\n```\nALTER SESSION SET GEOGRAPHY_OUTPUT_FORMAT = 'GEOJSON';\n```\n\nIn the following two queries you will familiarize yourself with `Overture Maps - Points of Interest` data. First, check the size of the table:\n\n```\nSELECT COUNT(*) FROM OVERTURE_MAPS__PLACES.CARTO.PLACE;\n```\n\nIn the following query, you will examine a geography column containing data on health and medical facilities. \n\n```\nSELECT \n     NAMES['primary']::STRING AS NAME,\n     ADDRESS.value:element:locality::STRING AS CITY,\n     ADDRESS.value:element:region::STRING AS STATE,\n     ADDRESS.value:element:postcode::STRING AS POSTCODE,\n     ADDRESS.value:element:country::STRING AS COUNTRY,\n     GEOMETRY\nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE,\nLATERAL FLATTEN(INPUT =\u003E ADDRESSES:list) AS ADDRESS\nWHERE CATEGORIES['primary'] ='health_and_medical'\nLIMIT 100;\n```\n\n![assets/geo_ml_40.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_40.png)\n\nNote that while the column is named `GEOMETRY` in this data source, it is stored in a `GEOGRAPHY` column in Snowflake, using the coordinate system [ESPG:4326](https://epsg.io/4326), also known as [WGS 84](https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84). This coordinate system uses latitude and longitude as coordinates and is the most widely used coordinate system worldwide. If you are storing geospatial data using latitude and longitude, then the `GEOGRAPHY` data type is the most suitable for storing your data.\n\n\nThe contents of the `GEOMETRY` column in the output above, formatted as GeoJSON. \n\nRun the code below to update your session geography output format to [Well-Known Text (WKT)](https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry), which is arguably more readable.\n\n```\nALTER SESSION SET GEOGRAPHY_OUTPUT_FORMAT = 'WKT';\n```\n\nNow rerun the Overture maps query. Notice how the contents of the `GEOMETRY` column are displayed.\n\n```\nSELECT \n     NAMES['primary']::STRING AS NAME,\n     ADDRESS.value:element:locality::STRING AS CITY,\n     ADDRESS.value:element:region::STRING AS STATE,\n     ADDRESS.value:element:postcode::STRING AS POSTCODE,\n     ADDRESS.value:element:country::STRING AS COUNTRY,\n     geometry\nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE,\nLATERAL FLATTEN(INPUT =\u003E ADDRESSES:list) AS ADDRESS\nWHERE CATEGORIES['primary'] ='health_and_medical'\nLIMIT 100;\n```\n![assets/geo_ml_41.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_41.png)\n\n### Constructing geospatial objects\nYou can use constructor functions such as [ST_MAKEPOINT](https://docs.snowflake.com/en/sql-reference/functions/st_makepoint), [ST_MAKELINE](https://docs.snowflake.com/en/sql-reference/functions/st_makeline) and [ST_POLYGON](https://docs.snowflake.com/en/sql-reference/functions/st_makepolygon) to create geospatial objects. Run the code below to create a geo point from latitude and longitude.\n\n```\nSELECT ST_MAKEPOINT(-74.0266511, 40.6346599) GEO_POINT\n```\n\n![assets/geo_ml_42.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_42.png)\n\nAlternatively, you can use the TO_GEOGRAPHY constructor function to create geospatial values. [TO_GEOGRAPHY](https://docs.snowflake.com/en/sql-reference/functions/to_geography) is a general purpose constructor where [ST_MAKEPOINT](https://docs.snowflake.com/en/sql-reference/functions/st_makepoint) specifically makes a POINT object. Run the code below:\n\n```\nSELECT TO_GEOGRAPHY('POINT(-74.0266511 40.6346599)') GEO_POINT\n```\n\n![assets/geo_ml_42.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_42.png)\n\n### 4. Visualizing spatial data in Streamlit\nUsing Streamlit, you can visualize your data using tools like `st.map` or popular python packages like `pydeck`. \n\nAdd a new Python cell and run the code below to see how you can use `st.map` to show a point on a map.\n\n```\nimport streamlit as st\nimport pandas as pd\n\n# Define the coordinates for the point\nlatitude = 40.755702\nlongitude = -73.986226\n\n# Create a DataFrame with the point\ndata = pd.DataFrame({\n    'lat': [latitude],\n    'lon': [longitude]\n})\n\n# Display the map with the point\nst.title(\"Display a Points with st.map\")\nst.map(data)\n```\n\n![assets/geo_ml_43.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_43.png)\n\n### Accessing coordinates of a geospatial object\nSometimes you need to do the opposite - access individual coordinates in a geospatial object. You can do that with accessor functions [ST_X](https://docs.snowflake.com/en/sql-reference/functions/st_x) and [ST_Y](https://docs.snowflake.com/en/sql-reference/functions/st_y) to access longitude and latitude accordingly. Run the code below:\n\n```\nSELECT \n     NAMES['primary']::STRING AS NAME,\n     ST_X(GEOMETRY) AS LONGITUDE,\n     ST_Y(GEOMETRY) AS LATITUDE,\nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE,\nLATERAL FLATTEN(INPUT =\u003E ADDRESSES:list) AS ADDRESS\nWHERE CATEGORIES['primary'] ='health_and_medical'\nLIMIT 100;\n```\n\n![assets/geo_ml_44.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_44.png)\n\n### Finding the nearest points and calculating distances\nYou can use relationship and measurement functions to perform spatial joins and other analytical operations. For example, you can use [ST_DWITHIN](https://docs.snowflake.com/en/sql-reference/functions/st_dwithin) to find health facilities that are within a mile from you, and you can use [ST_DISTANCE](https://docs.snowflake.com/en/sql-reference/functions/st_distance) to measure the actual distance between points.\n\nRun the code below to obtain the ten nearest health facilities that are no more than approximately a mile (1,600 meters) away from a given point. The records are sorted by distance.\n\n```\nSELECT \n     NAMES['primary']::STRING AS NAME,\n     ST_X(GEOMETRY) AS LONGITUDE,\n     ST_Y(GEOMETRY) AS LATITUDE,\n     GEOMETRY,\n     ST_DISTANCE(GEOMETRY,TO_GEOGRAPHY('POINT(-73.986226 40.755702)'))::NUMBER(6,2) \n        AS DISTANCE_METERS \nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE\nWHERE CATEGORIES['primary'] ='health_and_medical' AND\nST_DWITHIN(GEOMETRY,ST_MAKEPOINT(-73.986226, 40.755702),1600) = TRUE \nORDER BY 5 LIMIT 10;\n```\n\n![assets/geo_ml_45.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_45.png)\n\nNotice that this query runs on a table with over 53M rows. Snowflake's geospatial data types are very efficient!\n\n# Creating multi-layered maps in Streamlit\nUsing Streamlit and Pydeck, you can create a multi-layered visualization. \n\nTake note of the name of your previous cell and run the command below in a python cell to convert the results of the previous query into a pandas dataframe. We will reference this dataframe in the visualization.\n\n```\ndf = query_9.to_pandas()\n```\n\nNow you will visualize the top 10 health facilities relative to the reference point. Pydeck supports multi-layered maps that can be customized with tooltips and other features.\n\n```\nimport streamlit as st\nimport pandas as pd\nimport pydeck as pdk\n\n# Define the coordinates for your specific location\nlatitude = 40.755702\nlongitude = -73.986226\n\n# Create a DataFrame for your location\nmy_location_df = pd.DataFrame({\n    'lat': [latitude],\n    'lon': [longitude]\n})\n\n# Create a PyDeck Layer for visualizing points with larger size and a tooltip for NAME\ndata_layer = pdk.Layer(\n    \"ScatterplotLayer\",\n    df,\n    get_position='[LONGITUDE, LATITUDE]',\n    get_radius=50,  # Adjust this value for larger points\n    get_fill_color='[255, 0, 0, 160]',  # Red color with transparency\n    pickable=True,\n    get_tooltip=['NAME'],  # Add NAME as a tooltip\n)\n\n# Create a PyDeck Layer for your location with a different color and size\nmy_location_layer = pdk.Layer(\n    \"ScatterplotLayer\",\n    my_location_df,\n    get_position='[lon, lat]',\n    get_radius=100,  # Larger radius to highlight your location\n    get_fill_color='[0, 0, 255, 200]',  # Blue color with transparency\n    pickable=True,\n)\n\n# Set the view on the map\nview_state = pdk.ViewState(\n    latitude=df['LATITUDE'].mean(),\n    longitude=df['LONGITUDE'].mean(),\n    zoom=13.5,  # Adjust zoom if needed\n    pitch=0,\n)\n\n# Define the tooltip\ntooltip = {\n    \"html\": \"\u003Cb\u003EFacility Name:\u003C/b\u003E {NAME}\",\n    \"style\": {\"color\": \"white\"}\n}\n\n# Render the map with both layers and tooltip\nr = pdk.Deck(\n    layers=[data_layer, my_location_layer],\n    initial_view_state=view_state,\n    map_style='mapbox://styles/mapbox/light-v10',\n    tooltip=tooltip\n)\n\nst.write('10 Nearest Health Facilities')\nst.pydeck_chart(r, use_container_width=True)\n```\n\n![assets/geo_ml_46.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_46.png)\n\n### Conclusion\n\nCongratulations! You have completed this introductory quickstart. You learn basic operations to construct, process and visualise geospatial data.\n\n## Energy grids analysis using GEOMETRY\n\n\u003E \n\u003E  Before starting with this lab, complete the preparation steps from `Setup your account` page.\n\n\u003E \n\u003E  This lab is [available](https://github.com/Snowflake-Labs/sf-guide-geospatial-analytics-ai-ml) as Snowflake Notebook.\n\n#### Overview\nGeospatial query capabilities in Snowflake are built upon a combination of data types and specialized query functions that can be used to parse, construct, and run calculations over geospatial objects. This guide will introduce you to the `GEOMETRY` data type, help you understand geospatial formats supported by Snowflake and walk you through the use of a variety of functions on sample geospatial data sets. \n\n#### What You’ll Learn\n* How to acquire geospatial data from the Snowflake Marketplace\n* How to load geospatial data from a Stage\n* How to interpret the `GEOMETRY` data type and how it differs from the `GEOGRAPHY`\n* How to understand the different formats that `GEOMETRY` can be expressed in\n* How to do spatial analysis using the `GEOMETRY` and `GEOGRAPHY` data types\n* How to use Python UDFs for reading Shapefiles and creating custom functions\n* How to visualise geospatial data using Streamlit\n\n#### What You’ll Build\nA sample use case that involves energy grids and LTE cell towers in the Netherlands You will answer the following questions:\n* What is the length of all energy grids in each municipality in the Netherlands?\n* What cell towers lack electricity cables nearby?\n\n### Acquire Marketplace Data and Analytics Toolbox\n\nThe first step in the guide is to acquire geospatial data sets that you can freely use to explore the basics of Snowflake's geospatial functionality.  The best place to acquire this data is the Snowflake Marketplace!  \n* Navigate to the `Marketplace` screen using the menu on the left side of the window\n* Search for `OpenCelliD` in the search bar\n* Find and click the` OpenCelliD - Open Database of Cell Towers` tile or just use [this](https://app.snowflake.com/marketplace/listing/GZSVZ8ON6J/dataconsulting-pl-opencellid-open-database-of-cell-towers) link\n\n![assets/geo_ml_47.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_47.png)\n\n* Once in the listing, click the big blue `Get` button\n\n![assets/geo_ml_48.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_48.png)\n\n* On the `Get Data` screen, change the name of the database from the default to `OPENCELLID`, as this name is shorter, and all future instructions will assume this name for the database.\n\n![assets/geo_ml_49.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_49.png)\n\nSimilarly to the above dataset, acquire [SedonaSnow](https://app.snowflake.com/marketplace/listing/GZTYZF0RTY3/wherobots-sedonasnow) application which extends Snowflake core geo features with more than 100 spatial functions. Navigate to the `Marketplace` screen using the menu on the left side of the window and find the `SedonaSnow`. Keep the the database name `SEDONASNOW` and optionally add more roles that can access the database.\n\n![assets/geo_ml_22.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_22.png)\n\nCongratulations! You have just acquired all the listings you need for this lab.\n\n### Setup your Account\n\nCreate a new database and schema where you will store datasets in the `GEOMETRY` data type. Run th following SQL:\n\n```\nCREATE DATABASE IF NOT EXISTS GEOLAB;\nCREATE SCHEMA IF NOT EXISTS GEOLAB.GEOMETRY;\nUSE SCHEMA GEOLAB.GEOMETRY;\n```\n\n### Load Data from External Storage\n\nYou already understand how to get data from Marketplace, let's try another way of getting data, namely, getting it from the external S3 storage. While you loading data you will learn formats supported by geospatial data types.\n\nFor this quickstart we have prepared a dataset with energy grid infrastructure (cable lines) in the Netherlands. It is stored in the CSV format in the public S3 bucket. To import this data, create an external stage using the following SQL command:\n\n```\nCREATE OR REPLACE STAGE geolab.geometry.geostage\nURL = 's3://sfquickstarts/vhol_spatial_analysis_geometry_geography/';\n```\n\nNow you will create a new table using the file from that stage. Run the following queries to create a new file format and a new table using the dataset stored in the Stage:\n\n```\n// Create file format\nCREATE OR REPLACE FILE FORMAT geocsv TYPE = CSV SKIP_HEADER = 1 FIELD_OPTIONALLY_ENCLOSED_BY = '\"';\n\nCREATE OR REPLACE TABLE geolab.geometry.nl_cables_stations AS \nSELECT to_geometry($1) AS geometry, \n       $2 AS id, \n       $3 AS type \nFROM @geostage/nl_stations_cables.csv (file_format =\u003E 'geocsv');\n```\n\nLook at the description of the table you just created by running the following queries: \n\n```\nDESC TABLE geolab.geometry.nl_cables_stations;\n```\n\nThe [desc or describe](https://docs.snowflake.com/en/sql-reference/sql/desc.html) command shows you the definition of the view, including the columns, their data type, and other relevant details. Notice the `geometry` column is defined as `GEOMETRY` type. \n\nSnowflake supports 3 primary geospatial formats and 2 additional variations on those formats. They are:\n\n* **GeoJSON**: a JSON-based standard for representing geospatial data\n* **WKT & EWKT**: a \"Well Known Text\" string format for representing geospatial data and the \"Extended\" variation of that format\n* **WKB & EWKB:** a \"Well Known Binary\" format for representing geospatial data in binary and the \"Extended\" variation of that format\n\nThese formats are supported for ingestion (files containing those formats can be loaded into a `GEOMETRY` typed column), query result display, and data unloading to new files. You don't need to worry about how Snowflake stores the data under the covers but rather how the data is displayed to you or unloaded to files through the value of session variables called `GEOMETRY_OUTPUT_FORMAT`.\n\nRun the query below to make sure the current format is GeoJSON:\n\n```\nALTER SESSION SET geometry_output_format = 'GEOJSON';\n```\n\nThe [alter session](https://docs.snowflake.com/en/sql-reference/sql/alter-session.html) command lets you set a parameter for your current user session, which in this case is  `GEOMETRY_OUTPUT_FORMAT`. The default value for those parameters is `'GEOJSON'`, so normally you wouldn't have to run this command if you want that format, but this guide wants to be certain the next queries are run with the `'GEOJSON'` output.\n\nNow run the following query against the `nl_cables_stations` table to see energy grids in the Netherlands.\n\n```\nSELECT geometry\nFROM nl_cables_stations\nLIMIT 5;\n```\nIn the result set, notice the `GEOMETRY` column and how it displays a JSON representation of spatial objects. It should look similar to this:\n\n```\n{\"coordinates\": [[[1.852040750000000e+05, 3.410349640000000e+05], \n[1.852044840000000e+05,3.410359860000000e+05]], \n[[1.852390240000000e+05,3.411219340000000e+05], \n... ,\n[1.852800600000000e+05,3.412219960000000e+05]]   ], \n\"type\": \"MultiLineString\" }\n```\n\nUnlike `GEOGRAPHY`, which treats all points as longitude and latitude on a spherical earth, `GEOMETRY` considers the Earth as a flat surface. More information about Snowflake's specification can be found [here](https://docs.snowflake.com/en/sql-reference/data-types-geospatial.html).\nIn this example it uses scientific notation and the numbers are much larger than latitude and longitude boundaries [-180; 180].\n\n![assets/geo_ml_57.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_57.png)\n\nNow look at the same query but in a different format. Run the following query:\n\n```\nALTER SESSION SET geometry_output_format = 'EWKT';\n```\n\nRun the previous `SELECT` query again and when done, examine the output in the `GEOMETRY` column.\n\n```\nSELECT geometry\nFROM nl_cables_stations\nLIMIT 5;\n```\n\nEWKT looks different from GeoJSON, and is arguably more readable. Here you can more clearly see the [geospatial object types](https://docs.snowflake.com/en/sql-reference/data-types-geospatial.html#geospatial-object-types), which are represented below in the example output:\n\n```\nSRID=28992;MULTILINESTRING((185204.075 341034.964,185204.484 341035.986), ... ,(185276.402 341212.688,185279.319 341220.196,185280.06 341221.996))\n```\n\nEWKT also shows the spatial reference identifier and in our example, you have a dataset in [Amersfoort / RD New](https://epsg.io/28992) spatial reference system, that is why the displayed SRID is 28992.\n\nLastly, look at the WKB output. Run the following query:\n\n```\nALTER SESSION SET geometry_output_format = 'WKB';\n```\n\nRun the query again:\n```\nSELECT geometry\nFROM nl_cables_stations\nLIMIT 5;\n```\n\nNow that you have a basic understanding of how the `GEOMETRY` data type works and what a geospatial representation of data looks like in various output formats, it's time to walk through a scenario that requires you to use constructors to load data.  You will do it while trying one more way of getting data, namely, from the Shapefile file stored in the external stage. \n\nOne of the files in the external stage contains the polygons of administrative boundaries in the Netherlands. The data is stored in [Shapefile format](https://en.wikipedia.org/wiki/Shapefile) which is not yet supported by Snowflake. But you can load this file using Python UDF and [Dynamic File Access feature](https://docs.snowflake.com/developer-guide/udf/python/udf-python-examples#label-udf-python-read-files). You will also use some packages available in the Snowflake Anaconda channel.\n\nRun the following query that creates a UDF to read shapfiles:\n\n```\nCREATE OR REPLACE FUNCTION geolab.geometry.py_load_geodata(PATH_TO_FILE string, filename string)\nRETURNS TABLE (wkt varchar, properties object)\nLANGUAGE PYTHON\nRUNTIME_VERSION = 3.8\nPACKAGES = ('fiona', 'shapely', 'snowflake-snowpark-python')\nHANDLER = 'GeoFileReader'\nAS $$\nfrom shapely.geometry import shape\nfrom snowflake.snowpark.files import SnowflakeFile\nfrom fiona.io import ZipMemoryFile\nclass GeoFileReader:        \n    def process(self, PATH_TO_FILE: str, filename: str):\n    \twith SnowflakeFile.open(PATH_TO_FILE, 'rb') as f:\n    \t\twith ZipMemoryFile(f) as zip:\n    \t\t\twith zip.open(filename) as collection:\n    \t\t\t\tfor record in collection:\n    \t\t\t\t\tyield (shape(record['geometry']).wkt, dict(record['properties']))\n$$;\n```\n\nThis UDF reads a Shapefile and returns its content as a table. Under the hood it uses geospatial libraries `fiona` and `shapely`.\nRun the following query to see the content of the uploaded shapefile.\n\n```\nALTER SESSION SET geometry_output_format = 'EWKT';\n\nSELECT to_geometry(wkt) AS geometry,\n       properties:NAME_1::string AS province_name,\n       properties:NAME_2::string AS municipality_name\nFROM table(py_load_geodata(build_scoped_file_url(@geolab.geometry.geostage, 'nl_areas.zip'), 'nl_areas.shp'));\n```\n\nThis query fails with the error *NotebookSqlException: 100383: Geometry validation failed: Geometry has invalid self-intersections. A self-intersection point was found at (559963, 5.71069e+06)*.\nThe constructor function determines if the shape is valid according to the [Open Geospatial Consortium’s Simple Feature Access / Common Architecture](https://www.ogc.org/standards/sfa) standard. If the shape is invalid, the function reports an error and does not create the GEOMETRY object. That is what happened in our example.\n\nTo fix this you can allow the ingestion of invalid shapes by setting the corresponding parameter to True. Let's run the SELECT statement again, but update the query to see how many shapes are invalid. Run the following query:\n\n```\nSELECT to_geometry(s =\u003E wkt, allowInvalid =\u003E True) AS geometry,\n       st_isvalid(geometry) AS is_valid,\n       properties:NAME_1::string AS province_name,\n       properties:NAME_2::string AS municipality_name\nFROM table(py_load_geodata(build_scoped_file_url(@geolab.geometry.geostage, 'nl_areas.zip'), 'nl_areas.shp'))\nORDER BY is_valid ASC;\n```\n\n![assets/geo_ml_50.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_50.png)\n\nThis query completed without error and now you see that the shape of the province Zeeland is invalid. Let's repair it by applying the [ST_MakeValid](https://sedona.apache.org/1.5.1/api/snowflake/vector-data/Function/#st_makevalid) function from SedonaSnow Native app:\n\n```\nSELECT SEDONASNOW.SEDONA.st_MakeValid(to_geometry(s =\u003E wkt, allowInvalid =\u003E True)) AS geometry,\n       st_isvalid(geometry) AS is_valid,\n       (CASE WHEN properties:TYPE_1::string IS NULL THEN 'Municipality' ELSE 'Province' END) AS type,\n       properties:NAME_1::string AS province_name,\n       properties:NAME_2::string AS municipality_name\nFROM table(py_load_geodata(build_scoped_file_url(@geolab.geometry.geostage, 'nl_areas.zip'), 'nl_areas.shp'))\nORDER BY is_valid ASC;\n```\n\n![assets/geo_ml_51.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_51.png)\n\nNow all shapes are valid and the data is ready to be ingested. One additional thing you should do is to set SRID, since otherwise it will be set to 0. This dataset is in the reference system [WGS 72 / UTM zone 31N](https://epsg.io/32231), so it makes sense to add the SRID=32231 to the constructor function.\n\nRun the following query:\n\n```\nCREATE OR REPLACE TABLE geolab.geometry.nl_administrative_areas AS\nSELECT ST_SETSRID(SEDONASNOW.SEDONA.ST_MakeValid(to_geometry(s =\u003E wkt, srid =\u003E 32231, allowInvalid =\u003E True)), 32231) AS geometry,\n       st_isvalid(geometry) AS is_valid,\n       (CASE WHEN properties:TYPE_1::string IS NULL THEN 'Municipality' ELSE 'Province' END) AS type,\n       properties:NAME_1::string AS province_name,\n       properties:NAME_2::string AS municipality_name\nFROM table(py_load_geodata(build_scoped_file_url(@geolab.geometry.geostage, 'nl_areas.zip'), 'nl_areas.shp'))\nORDER BY is_valid ASC;\n```\n\nExcellent! Now that all the datasets are successfully loaded, let's proceed to the next exciting step: the analysis.\n\n### Energy grids Analysis\n\nTo showcase the capabilities of the GEOMETRY data type, you will explore several use cases. In these scenarios, you'll assume you are an analyst working for an energy utilities company responsible for maintaining electrical grids.\n\n#### What is the length of the electricity cables?\nIn the first use case you will calculate the length of electrical cables your organization is responsible for in each administrative area within the Netherlands. You'll be utilizing two datasets: with power infrastructure of the Netherlands and the borders of Dutch administrative areas. First, let's check the sample of each dataset.\n\nRun the following query to see the content of `nl_cables_stations` table:\n\n```\nSELECT geometry, type\nFROM geolab.geometry.nl_cables_stations\nLIMIT 5;\n```\n\n![assets/geo_ml_52.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_52.png)\n\nThe spatial data is stored using the `GEOMETRY` data type and employs the Dutch mapping system, `Amersfoort / RD New` (SRID = 28992). \n\nTo view the contents of the table containing the boundaries of the administrative areas in the Netherlands, execute the following query:\n\n```\nSELECT *\nFROM geolab.geometry.nl_administrative_areas\nLIMIT 5;\n```\n![assets/geo_ml_53.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_53.png)\n\nIn order to compute the length of all cables per administrative area, it's essential that both datasets adhere to the same mapping system. You have two options: either project `nl_administrative_areas` to SRID 28992, or project `nl_cables_stations` to SRID 32231. For this exercise, let's choose the first option. Run the following query:\n\n```\nSELECT t1.province_name,\n       sum(st_length(t2.geometry)) AS cables_length\nFROM geolab.geometry.nl_administrative_areas AS t1,\n     geolab.geometry.nl_cables_stations AS t2\nWHERE st_intersects(st_transform(t1.geometry, 28992), t2.geometry)\n  AND t1.type = 'Province'\nGROUP BY 1\nORDER BY 2 DESC;\n```\n![assets/geo_ml_54.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_54.png)\n\nYou have five areas densely covered by electricity cables, those are the ones that your company is responsible for. For your first analysis, you will focus on these areas.\n\n#### What cell towers lack electricity cables nearby?\n\nIn many areas, especially rural or remote ones, cell towers might be located far from electricity grids. This can pose a challenge in providing a reliable power supply to these towers. They often rely on diesel generators, which can be expensive to operate and maintain and have environmental implications. Furthermore, power outages can lead to disruptions in mobile connectivity, impacting individuals, businesses, and emergency services.\n\nOur analysis aims to identify mobile cell towers that are not near an existing electricity grid. This information could be used to prioritize areas for grid expansion, to improve the efficiency of renewable energy source installations (like solar panels or wind turbines), or to consider alternative energy solutions.\n\nFor this and the next examples let's use `GEOGRAPHY` data type as it can be easily visualized using CARTO. As a first step, let's create `GEOGRAPHY` equivalents for the energy grids and boundaries tables. For that you need to project the `geometry` column in each of the tables into the mapping system WGS 84 (SRID=4326) and then convert to `GEOGRAPHY` data type. Run the following queries that create new tables and enable search optimization for each of them in order to increase the performance of spatial operations. \n\n```\n// Creating a table with GEOGRAPHY for nl_administrative_areas\nCREATE SCHEMA IF NOT EXISTS GEOLAB.GEOGRAPHY;\n\nCREATE OR REPLACE TABLE geolab.geography.nl_administrative_areas AS\nSELECT to_geography(st_transform(geometry, 4326)) AS geom,\n       type,\n       province_name,\n       municipality_name\nFROM geolab.geometry.nl_administrative_areas\nORDER BY st_geohash(geom);\n\n// Creating a table with GEOGRAPHY for nl_cables_stations\nCREATE OR REPLACE TABLE geolab.geography.nl_cables_stations AS\nSELECT to_geography(st_transform(geometry, 4326)) AS geom,\n       id,\n       type\nFROM geolab.geometry.nl_cables_stations\nORDER BY st_geohash(geom);\n```\n\nNow you will create a table with locations of cell towers stored as GEOGRAPHY, just like for the previous two tables. Run the following query:\n\n```\nCREATE OR REPLACE TABLE geolab.geography.nl_lte AS\nSELECT DISTINCT st_point(lon, lat) AS geom,\n                cell_range\nFROM OPENCELLID.PUBLIC.RAW_CELL_TOWERS t1\nWHERE mcc = '204' -- 204 is the mobile country code in the Netherlands\nAND radio='LTE'\n```\n\nFinally, you will find all cell towers that don't have an energy line within a 2-kilometer radius. For each cell tower you'll calculate the distance to the nearest electricity cable. You will use Streamlit library `pydeck` to visualise municipalities and locations of cell towers. \n\nYou can create visualisation either in Notebooks or as a Strealit app. As a preparation step you need to import pydeck library that you will use in this Lab. Navigate to the `Packages` drop-down  in the upper right of the Notebook (upper left of the Streamlit app) and search for `pydeck`. Click on `pydeck` to add it to the Python packages. Then run the following Python code:\n\n```\nimport streamlit as st\nimport pandas as pd\nimport pydeck as pdk\nimport json\nfrom snowflake.snowpark.context import get_active_session\n\nsession = get_active_session()\n\ndef get_celltowers() -\u003E pd.DataFrame:\n    return session.sql(f\"\"\"\n    SELECT province_name,\n    cells.geom\n    FROM geolab.geography.nl_lte cells\n    LEFT JOIN geolab.geography.nl_cables_stations cables\n    ON st_dwithin(cells.geom, cables.geom, 2000)\n    JOIN geolab.geography.nl_administrative_areas areas \n    ON st_contains(areas.geom, cells.geom)\n    WHERE areas.type = 'Municipality'\n    AND areas.province_name in ('Noord-Brabant', 'Overijssel', 'Limburg', 'Groningen', 'Drenthe')\n    AND cables.geom IS NULL; \"\"\").to_pandas()\n\ndef get_boundaries() -\u003E pd.DataFrame:\n    return session.sql(f\"\"\"\n        SELECT st_simplify(GEOM, 10) as geom, municipality_name\n        FROM geolab.geography.nl_administrative_areas\n        WHERE type = 'Municipality';\n    \"\"\").to_pandas()\n\n\nboundaries = get_boundaries()\nboundaries[\"coordinates\"] = boundaries[\"GEOM\"].apply(lambda row: json.loads(row)[\"coordinates\"][0])\n\ncelltowers = get_celltowers()\ncelltowers[\"lon\"] = celltowers[\"GEOM\"].apply(lambda row: json.loads(row)[\"coordinates\"][0])\ncelltowers[\"lat\"] = celltowers[\"GEOM\"].apply(lambda row: json.loads(row)[\"coordinates\"][1])\n\nlayer_celltowers = pdk.Layer(\n            \"ScatterplotLayer\",\n            celltowers,\n            get_position=[\"lon\", \"lat\"],\n            id=\"celltowers\",\n            stroked=True,\n            filled=True,\n            extruded=False,\n            wireframe=True,\n            get_fill_color=[233, 43, 65],\n            get_line_color=[233, 43, 65],\n            get_radius=300,\n            auto_highlight=True,\n            pickable=False,\n        )\n\nlayer_boundaries = pdk.Layer(\n    \"PolygonLayer\",\n    data=boundaries,\n    id=\"province-layer\",\n    get_polygon=\"coordinates\",\n    extruded=False,\n    opacity=0.9,\n    wireframe=True,\n    pickable=True,\n    stroked=True,\n    filled=True,\n    line_width_min_pixels=1,\n    get_line_color=[17, 86, 127],       # Red color for the border\n    get_fill_color=[43, 181, 233, 30],  # Blue fill with transparency\n    coverage=1\n)\n\n\nst.pydeck_chart(pdk.Deck(\n    map_style=None,\n    initial_view_state=pdk.ViewState(\n        latitude=51.97954426323304,\n        longitude=5.626041932127842, \n        # pitch=45, \n        zoom=8),\n    tooltip={\n            'html': '\u003Cb\u003EProvince name:\u003C/b\u003E {MUNICIPALITY_NAME}',\n             'style': {\n                 'color': 'white'\n                 }\n            },\n    layers=[layer_boundaries, layer_celltowers],\n))\n```\n\n![assets/geo_ml_56.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_56.png)\n\nAnother way to visualise geospatial data is using open-source geo analytics tool QGIS. Do the following steps:\n* Install the latest [Long Term Version of QGIS](https://qgis.org/download/)\n* Install Snowflake conector. Go to `Plugins` \u003E `All`, search for `Snowflake Connector for QGIS` and click `Install Plugin`.\n* Go to `Layer` \u003E `Data Source Manager` and create a new connection to Snowflake. Call it `SNOWFLAKE` (all letters capital). [Check](https://github.com/snowflakedb/qgis-snowflake-connector?tab=readme-ov-file#getting-started) the documentation to learn mor on how to create new coonection\n* [Download](https://sfquickstarts.s3.us-west-1.amazonaws.com/vhol_spatial_analysis_geometry_geography/energy_grids_nl.qgz) a QGIS project that we created for you and open it in QGIS.\n* If previous steps done correctly, you should be able to see the following layers in QGIS\n    * `ENERGY_GRIDS` (LINESTRING and MULTILINESTRING) - energy frids for Noord-Brabant, Overijssel, Limburg, Groningen, and Drenthe.\n    * `CELL_TOWERS_WITHOUT_CABLES` - cell towers in the regions above that don't have energy grids within radius of 2km.\n    * `Municipalities` (POLYGON and MULTIPOLYGON) - Boundaries of Dutch municipalities.\n\n\n### Conclusion\n\nIn this guide, you acquired geospatial data from the Snowflake Marketplace, explored how the `GEOMETRY` data type works and how it differs from `GEOGRAPHY`. You converted one data type into another and queried geospatial data using parser, constructor, transformation, and used geospatial joins. You then saw how geospatial objects could be visualized using CARTO.\n\nYou are now ready to explore the larger world of Snowflake geospatial support and geospatial functions.\n\n### What we've covered\n* How to acquire a shared database from the Snowflake Marketplace and from External and internal storages.\n* The GEOMETRY data type, its formats GeoJSON, WKT, EWKT, WKB, and EWKB, and how to switch between them.\n* How to use constructors like TO_GEOMETRY, ST_MAKELINE.\n* How to reproject between SRIDs using ST_TRANSFORM.\n* How to perform relational calculations like ST_DWITHIN and ST_INTERSECTS.\n* How to perform measurement calculations like ST_LENGTH.\n* How to use Python UDFs for reading Shapefiles and creating custom functions.\n* How to visualise geospatial data using Streamlit and QGIS\n\n## Geocoding and Reverse Geocoding\n\n\n\u003E \n\u003E  Before starting with this lab, complete the preparation steps from `Setup your account` page.\n\n\u003E \n\u003E  This lab is [available](https://github.com/Snowflake-Labs/sf-guide-geospatial-analytics-ai-ml) as Snowflake Notebook.\n\nIn this lab, we will demonstrate how to perform geocoding and reverse geocoding using datasets and applications from the Marketplace. You will learn how to:\n- Perform address cleansing\n- Convert an address into a location (geocoding)\n- Convert a location into an address (reverse geocoding)\n\nFor the most precise and reliable geocoding results, we recommend using specialized services like [Mapbox](https://app.snowflake.com/marketplace/listing/GZT0ZIFQPEA/mapbox-mapbox-geocoding-analysis-tools) or [TravelTime](https://app.snowflake.com/marketplace/listing/GZ2FSZKSSH1/traveltime-technologies-ltd-traveltime). While the methods described in this Lab can be useful, they may not always achieve the highest accuracy, especially in areas with sparse data or complex geographic features. If your application demands extremely precise geocoding, consider investing in a proven solution with guaranteed accuracy and robust support.\n\nHowever, many companies seek cost-effective solutions for geocoding large datasets. In such cases, supplementing specialized services with free datasets can be a viable approach. Datasets provided by the [Overture Maps Foundation](https://overturemaps.org/) or [OpenAddresses](https://openaddresses.io/) can be valuable resources for building solutions that are \"good enough\", especially when some accuracy can be compromised in favor of cost-efficiency. It's essential to evaluate your specific needs and constraints before selecting a geocoding approach.\n\n### Step 1. Data acquisition\n\nFor this project you will use a dataset with locations of restaurants and cafes in Berlin from the [CARTO Academy](https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9J2/carto-carto-academy-data-for-tutorials) Marketplace listing.\n* Navigate to the `Marketplace` screen using the menu on the left side of the window\n* Search for `CARTO Academy` in the search bar\n* Find and click the `CARTO Academy - Data for tutorials` tile\n* Once in the listing, click the big blue `Get` button\n\n\u003E \n\u003E  On the `Get` screen, you may be prompted to complete your `user profile` if you have not done so before. Click the link as shown in the screenshot below. Enter your name and email address into the profile screen and click the blue `Save` button. You will be returned to the `Get` screen.\n\n![assets/geo_ml_10.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_10.png)\n\nAnother dataset that you will use in this Lab is [Worldwide Address Data](https://app.snowflake.com/marketplace/listing/GZSNZ7F5UT/starschema-worldwide-address-data) and you can also get it from the Snowflake Marketplace. It's a free dataset from the OpenAddresses project that allows Snowflake customers to map lat/long information to address details. \n- Search for `Worldwide Address Data` in the search bar\n- Find and click on the corresponding dataset from Starschema\n\n![assets/geo_ml_20.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_20.png)\n\n- On the `Get Data` screen, don't change the name of the database from `WORLDWIDE_ADDRESS_DATA`.\n \n![assets/geo_ml_21.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_21.png)\n\nNice! You have just got two listings that you will need for this project.\n\n### Step 2. Data Preparation\nTo showcase geocoding techniques in this lab, and to evaluate the quality of our approach you will use a table `CARTO_ACADEMY__DATA_FOR_TUTORIALS.CARTO.DATAAPPEAL_RESTAURANTS_AND_CAFES_BERLIN_CPG` with locations of restaurants and cafes in Berlin. If you look into that table you will notice that some records don't have full or correct information in the `STREET_ADDRESS` column. To be able to calculate the correct quality metrics in this lab lets do a simple cleanup of the low quality datapoint. Run the following query to create a table that has only records that have 5-digits postcode and those records are in Berlin.\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.GEOCODING_ADDRESSES AS\nSELECT * \nFROM CARTO_ACADEMY__DATA_FOR_TUTORIALS.CARTO.DATAAPPEAL_RESTAURANTS_AND_CAFES_BERLIN_CPG\nWHERE REGEXP_SUBSTR(street_address, '(\\\\d{5})') is not null\nAND city ILIKE 'berlin';\n```\nIf you check the size of `ADVANCED_ANALYTICS.PUBLIC.GEOCODING_ADDRESSES` table you'll see that it has about 10K rows.\n\nThe Worldwide Address Data dataset contains more than 500M addresses around the world and we will use it for geocoding and reverse geocoding. However some addresses in that dataset contain addresses with coordinates outside of the allowed boundaries for latitude and longitude. Run the following query to create a new table that filters out those \"invalid\" records and includes a new column, `LOCATION`, which stores the locations in the `GEOGRAPHY` type:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.OPENADDRESS AS\nSELECT ST_POINT(lon, lat) as location, *\nFROM WORLDWIDE_ADDRESS_DATA.ADDRESS.OPENADDRESS\nWHERE lon between -180 and 180\nAND lat between -90 and 90;\n```\n\nNow when all your data is ready and clean, you can proceed to the actual use cases.\n\n\n### Step 2. Data Cleansing\nCustomer-provided address data is often incomplete or contains spelling mistakes. If you plan to perform geocoding on that data, it would be a good idea to include address cleansing as a preparation step.\n\nIn this step, you will prepare a prompt to run the data cleansing. For this task, you will use the [CORTEX.COMPLETE()](https://docs.snowflake.com/en/sql-reference/functions/complete-snowflake-cortex) function because it is purpose-built for data processing and data generation tasks. First, let's create a Cortex role. In the query below, replace AA with the username you used to log in to Snowflake.\n\n```\nCREATE ROLE IF NOT EXISTS cortex_user_role;\nGRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE cortex_user_role;\n\nGRANT ROLE cortex_user_role TO USER AA;\n```\nYou are now ready to provide the CORTEX.COMPLETE() function with instructions on how to perform address cleansing. Specifically, using a table of Berlin restaurants, you'll create a new table with an additional column `parsed_address`, which is the result of the `CORTEX.COMPLETE()` function. For complex processing like this, you will use [mistral-8x7b](https://docs.snowflake.com/en/user-guide/snowflake-cortex/llm-functions#availability), a very capable open-source LLM created by Mistral AI. Essentially, we want to parse the address stored as a single string into a JSON object that contains each component of the address as a separate key.\n\nAs a general rule when writing a prompt, the instructions should be simple, clear, and complete. For example, you should clearly define the task as parsing an address into a JSON object. It's important to define the constraints of the desired output; otherwise, the LLM may produce unexpected results. Below, you specifically instruct the LLM to parse the address stored as text and explicitly tell it to respond in JSON format.\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES as\nSELECT geom, geoid, street_address, name,\n    snowflake.cortex.complete('mixtral-8x7b', \n    concat('Task: Your job is to return a JSON formatted response that normalizes, standardizes, and enriches the following address,\n            filling in any missing information when needed: ', street_address, \n            'Requirements: Return only in valid JSON format (starting with { and ending with }).\n            The JSON response should include the following fields:\n            \"number\": \u003C\u003Chouse_number\u003E\u003E,\n            \"street\": \u003C\u003Cstreet_name\u003E\u003E,\n            \"city\": \u003C\u003Ccity_name\u003E\u003E,\n            \"postcode\": \u003C\u003Cpostcode_value\u003E\u003E,\n            \"country\": \u003C\u003CISO_3166-1_alpha-2_country_code\u003E\u003E.\n            Values inside \u003C\u003C\u003E\u003E must be replaced with the corresponding details from the address provided.\n            - If a value cannot be determined, use \"Null\".\n            - No additional fields or classifications should be included beyond the five categories listed.\n            - Country code must follow the ISO 3166-1 alpha-2 standard.\n            - Do not add comments or any other non-JSON text.\n            - Use Latin characters for street names and cities, avoiding Unicode alternatives.\n            Examples:\n            Input: \"123 Mn Stret, San Franscico, CA\"\n            Output: {\"number\": \"123\", \"street\": \"Main Street\", \"city\": \"San Francisco\", \"postcode\": \"94105\", \"country\": \"US\"}\n            Input: \"45d Park Avnue, New Yrok, NY 10016\"\n            Output: {\"number\": \"45d\", \"street\": \"Park Avenue\", \"city\": \"New York\", \"postcode\": \"10016\", \"country\": \"US\"}\n            Input: \"10 Downig Stret, Londn, SW1A 2AA, United Knidom\"\n            Output: {\"number\": \"10\", \"street\": \"Downing Street\", \"city\": \"London\", \"postcode\": \"SW1A 2AA\", \"country\": \"UK\"}\n            Input: \"4 Avneu des Champs Elyses, Paris, France\"\n            Output: {\"number\": \"4\", \"street\": \"Avenue des Champs-Élysées\", \"city\": \"Paris\", \"postcode\": \"75008\", \"country\": \"FR\"}\n            Input: \"1600 Amiphiteatro Parkway, Montain View, CA 94043, USA\"\n            Output: {\"number\": \"1600\", \"street\": \"Amphitheatre Parkway\", \"city\": \"Mountain View\", \"postcode\": \"94043\", \"country\": \"US\"}\n            Input: \"Plaza de Espana, 28c, Madird, Spain\"\n            Output: {\"number\": \"28c\", \"street\": \"Plaza de España\", \"city\": \"Madrid\", \"postcode\": \"28008\", \"country\": \"ES\"}\n            Input: \"1d Prinzessinenstrase, Berlín, 10969, Germany\"\n            Output: {\"number\": \"1d\", \"street\": \"Prinzessinnenstraße\", \"city\": \"Berlin\", \"postcode\": \"10969\", \"country\": \"DE\"} ')) as parsed_address \n        FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODING_ADDRESSES;\n```\n\nOn a `LARGE` warehouse, which we used in this quickstart, the query completed in about 13 minutes. However, on a smaller warehouse, the completion time is roughly the same. We don't recommend using a warehouse larger than `MEDIUM` for CORTEX LLM functions, as it won't significantly reduce execution time. If you plan to execute complex processing with LLM on a large dataset, it's better to split the dataset into chunks up to 100K rows each and run multiple jobs in parallel using an `X-Small` warehouse. A rule of thumb is that on an `X-Small`, data cleansing of 1,000 rows can be done within 90 seconds, which costs about 5 cents.\n\nNow, you will convert the parsed address into JSON type:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES AS\nSELECT geoid, geom, street_address, name,\nTRY_PARSE_JSON(parsed_address) AS parsed_address,\nFROM ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES;\n```\n\nRun the following query to check what the result of cleansing looks like in the `PARSED_ADDRESS` column and compare it with the actual address in the `STREET_ADDRESS` column.\n\n```\nALTER SESSION SET GEOGRAPHY_OUTPUT_FORMAT='WKT';\n\nSELECT TOP 10 * FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES;\n```\n\nYou also can notice that 23 addresses were not correctly parsed, but if you look into the `STREET_ADDRESS` column of those records using the following query, you can understand why they were not parsed: in most cases there are some address elements missing in the initial address.\n\n```\nSELECT * FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES\nWHERE parsed_address IS NULL;\n```\n\n### Step3. Geocoding\n\nIn this step, you will use the Worldwide Address Data to perform geocoding. You will join this dataset with your cleansed address data using country, city, postal code, street, and building number as keys. For street name comparison, you will use [Jaro-Winkler distance](https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance) to measure similarity between the two strings. You should use a sufficiently high similarity threshold but not 100%, which would imply exact matches. Approximate similarity is necessary to account for potential variations in street names, such as \"Street\" versus \"Straße\".\n\nTo the initial table with actual location and address, you will add columns with geocoded and parsed values for country, city, postcode, street, and building number. Run the following query:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.GEOCODED AS\nSELECT \n    t1.name,\n    t1.geom AS actual_location,\n    t2.location AS geocoded_location, \n    t1.street_address as actual_address,\n    t2.street as geocoded_street, \n    t2.postcode as geocoded_postcode, \n    t2.number as geocoded_number, \n    t2.city as geocoded_city\nFROM ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES t1\nLEFT JOIN ADVANCED_ANALYTICS.PUBLIC.OPENADDRESS t2\nON t1.parsed_address:postcode::string = t2.postcode\nAND t1.parsed_address:number::string = t2.number\nAND LOWER(t1.parsed_address:country::string) = LOWER(t2.country)\nAND LOWER(t1.parsed_address:city::string) = LOWER(t2.city)\nAND JAROWINKLER_SIMILARITY(LOWER(t1.parsed_address:street::string), LOWER(t2.street)) \u003E 95;\n```\n\nNow let's analyze the results of geocoding and compare the locations we obtained after geocoding with the original addresses. First, let's see how many addresses we were not able to geocode using this approach.\n\n```\nSELECT count(*) FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODED\nWHERE geocoded_location IS NULL;\n```\n\nIt turned out that 2,081 addresses were not geocoded, which is around 21% of the whole dataset. Let's see how many geocoded addresses deviate from the original location by more than 200 meters.\n\n```\nSELECT COUNT(*) FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODED\nWHERE ST_DISTANCE(actual_location, geocoded_location) \u003E 200;\n```\n\nIt seems there are 174 addresses. Let's examine random records from these 174 addresses individually by running the query below. You can visualize coordinates from the table with results using [this](https://clydedacruz.github.io/openstreetmap-wkt-playground/) service (copy-paste `GEOCODED_LOCATION` and `ACTUAL_LOCATION` values). \n\n```\nSELECT * FROM ADVANCED_ANALYTICS.PUBLIC.GEOCODED\nWHERE ST_DISTANCE(actual_location, geocoded_location) \u003E 200;\n```\n\nYou can see that in many cases, our geocoding provided the correct location for the given address, while the original location point actually corresponds to a different address. Therefore, our approach returned more accurate locations than those in the original dataset. Sometimes, the \"ground truth\" data contains incorrect data points.\n\nIn this exercise, you successfully geocoded more than 78% of the entire dataset. To geocode the remaining addresses that were not geocoded using this approach, you can use paid services such as [Mapbox](https://app.snowflake.com/marketplace/listing/GZT0ZIFQPEA/mapbox-mapbox-geocoding-analysis-tools) or [TravelTime](https://app.snowflake.com/marketplace/listing/GZ2FSZKSSH1/traveltime-technologies-ltd-traveltime). However, you managed to reduce the geocoding cost by more than four times compared to what it would have been if you had used those services for the entire dataset.\n\n### Step 4. Reverse Geocoding\n\nIn the next step, we will do the opposite - for a given location, we will get the address. Often, companies have location information and need to convert it into the actual address. Similar to the previous example, the best way to do reverse geocoding is to use specialized services, such as Mapbox or TravelTime. However, there are cases where you're ready to trade off between accuracy and cost. For example, if you don't need an exact address but a zip code would be good enough. In this case, you can use free datasets to perform reverse geocoding.\n\nTo complete this exercise, we will use the nearest neighbor approach. For locations in our test dataset (`ADVANCED_ANALYTICS.PUBLIC.GEOCODING_ADDRESSES` table), you will find the closest locations from the Worldwide Address Data. Let's first create a procedure that, for each row in the given table with addresses, finds the closest address from the Worldwide Address Data table within the radius of 5km. To speed up the function we will apply an iterative approach to the neighbor search - start from 10 meters and increase the search radius until a match is found or the maximum radius is reached. Run the following query:\n\n```\nCREATE OR REPLACE PROCEDURE GEOCODING_EXACT(\n    NAME_FOR_RESULT_TABLE TEXT,\n    LOCATIONS_TABLE_NAME TEXT,\n    LOCATIONS_ID_COLUMN_NAME TEXT,\n    LOCATIONS_COLUMN_NAME TEXT,\n    WWAD_TABLE_NAME TEXT,\n    WWAD_COLUMN_NAME TEXT\n)\nRETURNS TEXT\nLANGUAGE SQL\nAS $$\nDECLARE\n    -- Initialize the search radius to 10 meters.\n    RADIUS REAL DEFAULT 10.0;\nBEGIN\n    -- **********************************************************************\n    -- Procedure: GEOCODING_EXACT\n    -- Description: This procedure finds the closest point from the Worldwide \n    --              Address Data table for each location in the LOCATIONS_TABLE. \n    --              It iteratively increases the search radius until a match is \n    --              found or the maximum radius is reached.\n    -- **********************************************************************\n\n    -- Create or replace the result table with the required schema but no data.\n    EXECUTE IMMEDIATE '\n        CREATE OR REPLACE TABLE ' || NAME_FOR_RESULT_TABLE || ' AS\n        SELECT\n            ' || LOCATIONS_ID_COLUMN_NAME || ',\n            ' || LOCATIONS_COLUMN_NAME || ' AS LOCATION_POINT,\n            ' || WWAD_COLUMN_NAME || ' AS CLOSEST_LOCATION_POINT,\n            t2.NUMBER,\n            t2.STREET,\n            t2.UNIT,\n            t2.CITY,\n            t2.DISTRICT,\n            t2.REGION,\n            t2.POSTCODE,\n            t2.COUNTRY,\n            0.0::REAL AS DISTANCE\n        FROM\n            ' || LOCATIONS_TABLE_NAME || ' t1,\n            ' || WWAD_TABLE_NAME || ' t2\n        LIMIT 0';\n\n-- Define a sub-query to select locations not yet processed.\n    LET REMAINING_QUERY := '\n        SELECT\n            ' || LOCATIONS_ID_COLUMN_NAME || ',\n            ' || LOCATIONS_COLUMN_NAME || '\n        FROM\n            ' || LOCATIONS_TABLE_NAME || '\n        WHERE\n            NOT EXISTS (\n                SELECT 1\n                FROM ' || NAME_FOR_RESULT_TABLE || ' tmp\n                WHERE ' || LOCATIONS_TABLE_NAME || '.' || LOCATIONS_ID_COLUMN_NAME || ' = tmp.' || LOCATIONS_ID_COLUMN_NAME || '\n            )';\n\n-- Iteratively search for the closest point within increasing radius.\n    FOR I IN 1 TO 10 DO\n-- Insert closest points into the result table for \n-- locations within the current radius.\n        EXECUTE IMMEDIATE '\n            INSERT INTO ' || NAME_FOR_RESULT_TABLE || '\n            WITH REMAINING AS (' || :REMAINING_QUERY || ')\n            SELECT\n                ' || LOCATIONS_ID_COLUMN_NAME || ',\n                ' || LOCATIONS_COLUMN_NAME || ' AS LOCATION_POINT,\n                points.' || WWAD_COLUMN_NAME || ' AS CLOSEST_LOCATION_POINT,\n                points.NUMBER,\n                points.STREET,\n                points.UNIT,\n                points.CITY,\n                points.DISTRICT,\n                points.REGION,\n                points.POSTCODE,\n                points.COUNTRY,\n                ST_DISTANCE(' || LOCATIONS_COLUMN_NAME || ', points.' || WWAD_COLUMN_NAME || ') AS DISTANCE\n            FROM\n                REMAINING\n            JOIN\n                ' || WWAD_TABLE_NAME || ' points\n            ON\n                ST_DWITHIN(\n                    REMAINING.' || LOCATIONS_COLUMN_NAME || ',\n                    points.' || WWAD_COLUMN_NAME || ',\n                    ' || RADIUS || '\n                )\n            QUALIFY\n                ROW_NUMBER() OVER (\n                    PARTITION BY ' || LOCATIONS_ID_COLUMN_NAME || '\n                    ORDER BY DISTANCE\n                ) \u003C= 1';\n\n        -- Double the radius for the next iteration.\n        RADIUS := RADIUS * 2;\n    END FOR;\nEND\n$$;\n```\nRun the next query to call that procedure and store results of reverse geocoding to `ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED` table:\n\n```\nCALL GEOCODING_EXACT('ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED', 'ADVANCED_ANALYTICS.PUBLIC.GEOCODING_ADDRESSES', 'GEOID', 'GEOM', 'ADVANCED_ANALYTICS.PUBLIC.OPENADDRESS', 'LOCATION');\n```\nThis query completed in 5.5 minutes on `LARGE` warehouse, which corresponds to about 2 USD. Let's now compare the address we get after the reverse geocoding (`ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED` table) with the table that has the original address.\n\n```\nSELECT t1.geoid, \n    t2.street_address AS actual_address,\n    t1.street || ' ' || t1.number || ', ' || t1.postcode || ' ' || t1.city  || ', ' || t1.country AS geocoded_address\nFROM ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED t1\nINNER JOIN ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES t2\n    ON t1.geoid = t2.geoid\nWHERE t1.distance \u003C 100;\n```\n\nFor 9830 records, the closest addresses we found are within 100 meters from the original address. This corresponds to 98.7% of cases. As we mentioned earlier, often for analysis you might not need the full address, and knowing a postcode is already good enough. Run the following query to see for how many records the geocoded postcode is the same as the original postcode:\n\n```\nSELECT count(*)\nFROM ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED t1\nINNER JOIN ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES t2\n    ON t1.geoid = t2.geoid\nWHERE t2.parsed_address:postcode::string = t1.postcode::string;\n```\n\nThis query returned 9564 records,  about 96% of the dataset, which is quite a good result.\n\nOut of curiosity, let's see, for how many addresses the geocoded and initial address is the same up until the street name. Run the following query:\n\n```\nSELECT count(*)\nFROM ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED t1\nINNER JOIN ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES t2\n    ON t1.geoid = t2.geoid\nWHERE t2.parsed_address:postcode::string = t1.postcode\nAND LOWER(t2.parsed_address:country::string) = LOWER(t1.country)\nAND LOWER(t2.parsed_address:city::string) = LOWER(t1.city)\nAND JAROWINKLER_SIMILARITY(LOWER(t2.parsed_address:street::string), LOWER(t1.street)) \u003E 95;\n```\n\n82% of addresses correctly geocoded up to the street name. And to have a full picture, let's see how many records have the fully identical original and geocoded address:\n\n```\nSELECT count(*)\nFROM ADVANCED_ANALYTICS.PUBLIC.REVERSE_GEOCODED t1\nINNER JOIN ADVANCED_ANALYTICS.PUBLIC.GEOCODING_CLEANSED_ADDRESSES t2\n    ON t1.geoid = t2.geoid\nWHERE t2.parsed_address:postcode::string = t1.postcode\nAND t2.parsed_address:number::string = t1.number\nAND LOWER(t2.parsed_address:country::string) = LOWER(t1.country)\nAND LOWER(t2.parsed_address:city::string) = LOWER(t1.city)\nAND JAROWINKLER_SIMILARITY(LOWER(t2.parsed_address:street::string), LOWER(t1.street)) \u003E 95;\n```\n\nFor 61% of addresses we were able to do reverse geocoding that matches reference dataset up to the rooftop.\n\n### Conclusion\n\nIn this lab, you have learned how to perform geocoding and reverse geocoding using free datasets and open-source tools. While this approach may not provide the highest possible accuracy, it offers a cost-effective solution for processing large datasets where some degree of inaccuracy is acceptable. It's important to mention that Worldwide Address Data that has more than 500M addresses  for the whole world is one of many free datasets that you can get from Snowflake Marketplace and use for geocoding use cases. There are others, which you might consider for your use cases, here are just some examples:\n- [Overture Maps - Addresses](https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9NQ/carto-overture-maps-addresses) - if you mainly need to geocode addresses in North America, another good option would be to use this dataset that has more than 200M addresses.\n- [Snowflake Public Data](https://app.snowflake.com/marketplace/listing/GZTSZ290BV255) - includes a Global Government, Demographics & Geospatial database, with US addresses and POIs.\n- [French National Addresses](https://app.snowflake.com/marketplace/listing/GZT1ZQXT8U/atos-french-national-addresses) - contains about 26M addresses in France.\n- [Dutch Addresses & Buildings Registration (BAG)](https://app.snowflake.com/marketplace/listing/GZ1M7Z62O2A/tensing-dutch-addresses-buildings-registration-bag) - includes Dutch Addresses.\n\nThere is a high chance that datasets focused on particular counties have richer and more accurate data for those countries. And by amending queries from this lab you can find the best option for your needs. \n\n\n## Forecasting time series on a map\n\n\n\u003E \n\u003E  Before starting with this lab, complete preparation steps from `Setup your account` page.\n\nIn this lab, we aim to show you how to predict the number of trips in the coming hours in each area of New York. To accomplish this, you will ingest the raw data and then aggregate it by hour and region. For simplicity, you will use [Discrete Global Grid H3](https://www.uber.com/en-DE/blog/h3/). The result will be an hourly time series, each representing the count of trips originating from distinct areas. Before running prediction and visualizing results, you will enrich data with third-party signals, such as information about holidays and offline sports events.\n\nIn this lab you will learn how to:\n* Work with geospatial data\n* Enrich data with new features\n* Predict time-series of complex structure\n\nThis approach is not unique to trip forecasting but is equally applicable in various scenarios where predictive analysis is required. Examples include forecasting scooter or bike pickups, food delivery orders, sales across multiple retail outlets, or predicting the volume of cash withdrawals across an ATM network. Such models are invaluable for planning and optimization across various industries and services.\n\n### Step 1. Data acquisition\n\nThe New York Taxi and Limousine Commission (TLC) has provided detailed, anonymized customer travel data since 2009. Painted yellow cars can pick up passengers in any of the city's five boroughs. Raw data on yellow taxi rides can be found on the [TLC website](https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page). This data is divided into files by month. Each file contains detailed trip information, you can read about it [here](https://www.nyc.gov/assets/tlc/downloads/pdf/data_dictionary_trip_records_yellow.pdf). For our project, you will use an NY Taxi dataset for the 2014-2015 years from the [CARTO Academy](https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9J2/carto-carto-academy-data-for-tutorials) Marketplace listing.\n* Navigate to the `Marketplace` screen using the menu on the left side of the window\n* Search for `CARTO Academy` in the search bar\n* Find and click the `CARTO Academy - Data for tutorials` tile\n* Once in the listing, click the big blue `Get` button\n\n\u003E \n\u003E  On the `Get` screen, you may be prompted to complete your `user profile` if you have not done so before. Click the link as shown in the screenshot below. Enter your name and email address into the profile screen and click the blue `Save` button. You will be returned to the `Get` screen.\n\n![assets/geo_ml_10.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_10.png)\n\nAnother dataset you will use is events data and you can also get it from the Snowflake Marketplace. It is provided by PredictHQ and called [PredictHQ Quickstart Demo](https://app.snowflake.com/marketplace/listing/GZSTZ3TGTNLQM/predicthq-quickstart-demo).\n* Search for` PredictHQ Quickstart Demo` in the search bar\n* Find and click the `Quickstart Demo` tile\n\n![assets/geo_ml_4.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_4.png)\n\n* On the `Get Data` screen click `Get`.\n\n![assets/geo_ml_6.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_6.png)\n\nCongratulations! You have just created a shared database from a listing on the Snowflake Marketplace. \n\n### Step 2. Data transformation\n\nIn this step, you'll divide New York into uniformly sized regions and assign each taxi pick-up location to one of these regions. We aim to get a table with the number of taxi trips per hour for each region.\n\nTo achieve this division, you will use the Discrete Global Grid H3. H3 organizes the world into a grid of equal-sized hexagonal cells, with each cell identified by a unique code (either a string or an integer). This hierarchical grid system allows cells to be combined into larger cells or subdivided into smaller ones, facilitating efficient geospatial data processing.\n\nH3 offers 16 different resolutions for dividing areas into hexagons, ranging from resolution 0, where the world is segmented into 122 large hexagons, to resolution 15. At this resolution, each hexagon is less than a square meter, covering the world with approximately 600 trillion hexagons. You can read more about resolutions [here](https://h3geo.org/docs/core-library/restable/). For our task, we will use resolution 8, where the size of each hexagon is about 0.7 sq. km (0.3 sq. miles).\n\nAs a source of the trips data you will use `TLC_YELLOW_TRIPS_2014` and `TLC_YELLOW_TRIPS_2015` tables from the CARTO Academy listing. We are interested in the following fields:\n* Pickup Time\n* Dropoff Time\n* Pickup Latitude\n* Pickup Longitude\n* Dropoff Latitude\n* Dropoff Longitude\n\nFirst, specify the default Database, Schema and the Warehouse:\n```\nUSE ADVANCED_ANALYTICS.PUBLIC;\nUSE WAREHOUSE my_wh;\n```\n\nSince CARTO's tables contain raw data you might want to clean it before storing. In the following query you will do a few data cleaning steps:\n* Remove rows that are outside of latitude/longitude allowed values\n* Keep only trips with a duration longer than one minute and distances more than 10 meters.\n\nAnd since you are interested in trip data for 2014 and 2015 you need to union `TLC_YELLOW_TRIPS_2014` and `TLC_YELLOW_TRIPS_2015` tables. On average, the execution time on the LARGE warehouse is under 4 minutes.\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides AS\nSELECT CONVERT_TIMEZONE('UTC', 'America/New_York', to_timestamp(PICKUP_DATETIME::varchar)) PICKUP_TIME,\n       CONVERT_TIMEZONE('UTC', 'America/New_York', to_timestamp(DROPOFF_DATETIME::varchar)) DROPOFF_TIME,\n       st_point(PICKUP_LONGITUDE, PICKUP_LATITUDE) AS PICKUP_LOCATION,\n       st_point(DROPOFF_LONGITUDE, DROPOFF_LATITUDE) AS DROPOFF_LOCATION,\nFROM CARTO_ACADEMY__DATA_FOR_TUTORIALS.CARTO.TLC_YELLOW_TRIPS_2014\nWHERE pickup_latitude BETWEEN -90 AND 90\n  AND dropoff_latitude BETWEEN -90 AND 90\n  AND pickup_longitude BETWEEN -180 AND 180\n  AND dropoff_longitude BETWEEN -180 AND 180\n  AND st_distance(st_point(PICKUP_LONGITUDE, PICKUP_LATITUDE), st_point(DROPOFF_LONGITUDE, DROPOFF_LATITUDE)) \u003E 10\n  AND TIMEDIFF(MINUTE, PICKUP_TIME, DROPOFF_TIME) \u003E 1\nUNION ALL\nSELECT CONVERT_TIMEZONE('UTC', 'America/New_York', to_timestamp(PICKUP_DATETIME::varchar)) PICKUP_TIME,\n       CONVERT_TIMEZONE('UTC', 'America/New_York', to_timestamp(DROPOFF_DATETIME::varchar)) DROPOFF_TIME,\n       st_point(PICKUP_LONGITUDE, PICKUP_LATITUDE) AS PICKUP_LOCATION,\n       st_point(DROPOFF_LONGITUDE, DROPOFF_LATITUDE) AS DROPOFF_LOCATION,\nFROM CARTO_ACADEMY__DATA_FOR_TUTORIALS.CARTO.TLC_YELLOW_TRIPS_2015\nWHERE pickup_latitude BETWEEN -90 AND 90\n  AND dropoff_latitude BETWEEN -90 AND 90\n  AND pickup_longitude BETWEEN -180 AND 180\n  AND dropoff_longitude BETWEEN -180 AND 180\n  AND st_distance(PICKUP_LOCATION, DROPOFF_LOCATION) \u003E 10\n  AND TIMEDIFF(MINUTE, PICKUP_TIME, DROPOFF_TIME) \u003E 1;\n```\n\nNow you will create a table where, for each pair of timestamp/H3, we calculate the number of trips. You will strip off minutes and seconds and keep only hours.\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 AS\nSELECT TIME_SLICE(pickup_time, 60, 'minute', 'START') AS pickup_time,\n       H3_POINT_TO_CELL_string(pickup_location, 8) AS h3,\n       count(*) AS pickups\nFROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides\nGROUP BY 1, 2;\n```\n\nSince on resolution 8, you might have more than 1000 hexagons for New York, to speed up the training process, you will keep only hexagons that had more than 1M pickups in 2014.  This is shown in the following code block. \n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 \nAS WITH all_hexagons AS\n  (SELECT h3,\n          SUM(pickups) AS total_pickups\n   FROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3\n   WHERE year(pickup_time) = 2014\n   GROUP BY 1)\nSELECT t1.*\nFROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 t1\nINNER JOIN all_hexagons t2 ON t1.h3 = t2.h3\nWHERE total_pickups \u003E= 1000000;\n```\n\nIt's important to remember that if the raw data lacks records for a specific hour and area combination, the aggregated data for that period should be marked as 0. This step is crucial for accurate time series prediction. Run the following query to add records indicating that there were zero trips for any H3 location and timestamp pair without recorded trips.\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 AS\nWITH all_dates_hexagons AS (\n    SELECT DATEADD(HOUR, VALUE::int, '2014-01-01'::timestamp) AS pickup_time, h3\n    FROM TABLE(FLATTEN(ARRAY_GENERATE_RANGE(0, DATEDIFF('hour', '2014-01-01', '2015-12-31 23:59:00') + 1)))\n    CROSS JOIN (SELECT DISTINCT h3 FROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3)\n)\nSELECT t1.pickup_time, \nt1.h3, IFF(t2.pickups IS NOT NULL, t2.pickups, 0) AS pickups\nFROM all_dates_hexagons t1\nLEFT JOIN ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 t2 \nON t1.pickup_time = t2.pickup_time AND t1.h3 = t2.h3;\n```\n\n### Step 4. Data Enrichment\n\nIn this step, you will enhance our dataset with extra features that could improve the accuracy of our predictions. Cortex model for time series automatically encodes days of the week as a separate feature, but it makes sense to consider that public or school holidays could affect the demand for taxi services. Likewise, areas hosting sporting events might experience a surge in taxi pickups. To incorporate this insight, you will use data from [PredictHQ - Quickstart Demo](https://app.snowflake.com/marketplace/listing/GZSTZ3TGTNLQM/predicthq-quickstart-demo) listing, which provides information on events in New York for the years 2014-2015.\n\nRun the following query to enrich the data with holiday, and event information. For sports events, you will include only those with a high rank.\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 AS\nSELECT t1.*,\n       IFF(t2.category = 'school-holidays', 'school-holidays', 'None') AS school_holiday,\n       IFF(t3.category = 'public-holidays', ARRAY_TO_STRING(t3.labels, ', '), 'None') AS public_holiday,\n       IFF(t4.category = 'sports', t4.labels[0]::string, 'None') AS sport_event\nFROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 t1\nLEFT JOIN (SELECT distinct title, category, event_start, event_end, labels \n           FROM QUICKSTART_DEMO.PREDICTHQ.PREDICTHQ_EVENTS_SNOWFLAKE_SUMMIT_2024 \n           WHERE category = 'school-holidays' and title ilike 'New York%') t2 \n    ON DATE(t1.pickup_time) between t2.event_start AND t2.event_end\nLEFT JOIN (SELECT distinct title, category, event_start, event_end, labels \n           FROM QUICKSTART_DEMO.PREDICTHQ.PREDICTHQ_EVENTS_SNOWFLAKE_SUMMIT_2024 \n           WHERE array_contains('holiday-national'::variant, labels)) t3 \n    ON DATE(t1.pickup_time) between t3.event_start AND t3.event_end\nLEFT JOIN (SELECT * from QUICKSTART_DEMO.PREDICTHQ.PREDICTHQ_EVENTS_SNOWFLAKE_SUMMIT_2024 \n           WHERE phq_rank \u003E 70 and category = 'sports') t4 \n    ON t1.pickup_time = date_trunc('hour', t4.event_start) \n    AND t1.h3 = h3_point_to_cell_string(t4.geo, 8);\n```\n\n### Step 5. Training and Prediction\n\nIn this step, you'll divide our dataset into two parts: the Training set and the Prediction set. The Training set will be used to train our machine learning model. It will include data from the entirety of 2014 and part of 2015, going up to June 5th, 2015. Run the following query to create the Training set:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3_TRAIN AS\nSELECT *\nFROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3\nWHERE date(pickup_time) \u003C date('2015-06-05 12:00:00');\n```\n\nThe prediction set, on the other hand, will contain data for one week starting June 5th, 2015. This setup allows us to make predictions on data that wasn't used during training.\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3_PREDICT AS\nSELECT h3,\n       pickup_time,\n       SCHOOL_HOLIDAY,\n       PUBLIC_HOLIDAY,\n       SPORT_EVENT\nFROM ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3\nWHERE date(pickup_time) \u003E= date('2015-06-05')\nAND date(pickup_time) \u003C date('2015-06-12');\n```\n\nNow that you have the Training and Prediction sets, you can run your model training step. In this step, you will use Snowflake’s Cortex ML Forecasting function to train your `ny_taxi_rides_model`. You’re telling the function it should train on `ny_taxi_rides_h3_train` – and that this table contains data for multiple distinct time series (`series_colname =\u003E ‘h3’`),  one for each h3 in the table. The function will now automatically train one machine learning model for each h3. Note that you are also telling the model which column in our table to use as a timestamp and which column to treat as our “target” (i.e., the column you want to forecast). On average the query below completes in about 7 minutes on the LARGE warehouse.\n\n```\nCREATE OR REPLACE snowflake.ml.forecast ny_taxi_rides_model(\n  input_data =\u003E system$reference('table', 'ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3_TRAIN'), \n  series_colname =\u003E 'h3', \n  timestamp_colname =\u003E 'pickup_time', \n  target_colname =\u003E 'pickups');\n```\n\nNow you will predict the \"future\" demand for one week of test data. Run the following command to forecast demand for each H3 cell ID  and store your results in the \"forecasts\" table.\n\nSimilar to what you did in the training step, you specify the data the model should use to generate its forecasts (`ny_taxi_rides_h3_predict`) and indicate which columns to use for identifying unique H3 and for timestamps. \n\n```\nBEGIN\n    CALL ny_taxi_rides_model!FORECAST(\n        INPUT_DATA =\u003E SYSTEM$REFERENCE('TABLE', 'ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3_PREDICT'),\n        SERIES_COLNAME =\u003E 'h3',\n        TIMESTAMP_COLNAME =\u003E 'pickup_time',\n        CONFIG_OBJECT =\u003E {'prediction_interval': 0.95}\n    );\n    -- These steps store your predictions to a table.\n    LET x := SQLID;\n    CREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_model_forecast AS \n    SELECT series::string as h3,\n    ts AS pickup_time,\n    -- If any forecasts or prediction intervals are negative you need to convert them to zero. \n    CASE WHEN forecast \u003C 0 THEN 0 ELSE forecast END AS forecast,\n    CASE WHEN lower_bound \u003C 0 THEN 0 ELSE lower_bound END AS lower_bound,\n    CASE WHEN upper_bound \u003C 0 THEN 0 ELSE upper_bound END AS upper_bound\n    FROM TABLE(RESULT_SCAN(:x));\nEND;\n```\nCreate a table with predicted and actual results:\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_compare AS\nSELECT t1.h3, \n       t1.pickup_time, \n       t2.pickups, \n       round(t1.forecast, 0) as forecast\nFROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_model_forecast t1\nINNER JOIN ADVANCED_ANALYTICS.PUBLIC.NY_TAXI_RIDES_H3 t2\nON t1.h3 = t2.h3\nAND t1.pickup_time = t2.pickup_time;\n```\n\nNow you will generate evaluation metrics and store them in the `ny_taxi_rides_metrics` table:\n\n```\nBEGIN\n    CALL ny_taxi_rides_model!show_evaluation_metrics();\n    LET x := SQLID;\n    CREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_metrics AS \n    SELECT series::string as h3,\n           metric_value,\n           error_metric\n    FROM TABLE(RESULT_SCAN(:x));\nEND;\n```\n\nThe table `ny_taxi_rides_metrics` contains various metrics; please review what is available in the table. You should select a metric that allows uniform comparisons across all hexagons to understand the model's performance in each hexagon. Since trip volumes may vary among hexagons, the chosen metric should not be sensitive to absolute values. The Symmetric Mean Absolute Percentage Error ([SMAPE](https://en.wikipedia.org/wiki/Symmetric_mean_absolute_percentage_error)) would be a suitable choice. Create a table with the list of hexagons and the SMAPE value for each:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_metrics AS\nSELECT h3, metric_value AS smape \nFROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_metrics\nWHERE error_metric::string = 'SMAPE'\norder by 2 asc;\n```\n\n### Step 6. Visualization and analysis\n\nIn this step, you will visualize the actual and predicted results and think on how you can improve our model. Open `Projects \u003E Streamlit \u003E + Streamlit App`. Give the new app a name, for example `Demand Prediction - model analysis`, and pick `ADVANCED_ANALYTICS.PUBLIC` as an app location.\n\n![assets/geo_ml_7.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_7.png)\n\nClick on the packages tab and add `pydeck`, `branca` and `plotly` to the list of packages as our app will be using them. \n\n![assets/geo_ml_8.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_8.png)\n\nThen copy-paste the following code to the editor and click `Run`:\n\n```\nimport branca.colormap as cm\nimport datetime\nimport pandas as pd\nimport plotly.express as px\nimport pydeck as pdk\nimport streamlit as st\nfrom snowflake.snowpark.context import get_active_session\n\n@st.cache_data\ndef get_dataframe_from_raw_sql(query: str) -\u003E pd.DataFrame:\n    session = get_active_session()\n    pandas_df = session.sql(query).to_pandas()\n    return pandas_df\n\ndef pydeck_chart_creation(\n    chart_df: pd.DataFrame,\n    coordinates: tuple = (40.742, -73.984),\n    elevation_3d: bool = False,\n):\n    highest_count_df = 0 if chart_df is None else chart_df[\"COUNT\"].max()\n    st.image('https://sfquickstarts.s3.us-west-1.amazonaws.com/hol_geo_spatial_ml_using_snowflake_cortex/gradient.png')\n    st.pydeck_chart(\n        pdk.Deck(\n            map_style=None,\n            initial_view_state=pdk.ViewState(\n                latitude=coordinates[0],\n                longitude=coordinates[1],\n                pitch=45,\n                zoom=10,\n            ),\n            tooltip={\"html\": \"\u003Cb\u003E{H3}:\u003C/b\u003E {COUNT}\", \"style\": {\"color\": \"white\"}},\n            layers=[\n                pdk.Layer(\n                    \"H3HexagonLayer\",\n                    chart_df,\n                    get_hexagon=\"H3\",\n                    get_fill_color=\"COLOR\",\n                    get_line_color=\"COLOR\",\n                    get_elevation=f\"COUNT/{highest_count_df}\",\n                    auto_highlight=True,\n                    elevation_scale=10000 if elevation_3d else 0,\n                    pickable=True,\n                    elevation_range=[0, 300],\n                    extruded=True,\n                    coverage=1,\n                    opacity=0.3,\n                )\n            ],\n        )\n    )\n\ndef generate_linear_color_map(colors: list, quantiles):\n    return cm.LinearColormap(\n        colors,\n        vmin=quantiles.min(),\n        vmax=quantiles.max(),\n        index=quantiles,\n    )\n\ndef render_plotly_line_chart(chart_df: pd.DataFrame):\n    fig = px.line(\n        chart_df,\n        x=\"PICKUP_TIME\",\n        y=[\"PICKUPS\", \"FORECAST\"],\n        color_discrete_sequence=[\"#D966FF\", \"#126481\"],\n        markers=True,\n    )\n\n    fig.update_layout(yaxis_title=\"Pickups\", xaxis_title=\"\")\n    st.plotly_chart(fig, theme=\"streamlit\", use_container_width=True)\n\nst.set_page_config(layout=\"wide\", initial_sidebar_state=\"expanded\")\nst.title(\"NY Pickup Location App :balloon:\")\nst.write(\"\"\"An app that visualizes geo-temporal data from NY taxi pickups using H3 and time series. \n\t\t\tIt can be useful to visualize marketplace signals that are distributed spatially and temporally.\"\"\")\n\nAVGLATITUDELONGITUDE = \"\"\"SELECT\nAVG(ST_Y(H3_CELL_TO_POINT(h3))) AS lat,\nAVG(ST_X(h3_cell_to_point(h3))) AS lon,\nFROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_compare\"\"\"\n\nSQLQUERYTIMESERIES = \"\"\"SELECT pickup_time, h3, forecast, pickups\nFROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_compare\"\"\"\n\nSQLQUERYMETRICS = \"\"\"SELECT * FROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_metrics\"\"\"\n\ndf_avg_lat_long = get_dataframe_from_raw_sql(AVGLATITUDELONGITUDE)\navg_coordinate = (df_avg_lat_long.iloc[0, 0], df_avg_lat_long.iloc[0, 1])\ndf_metrics = get_dataframe_from_raw_sql(SQLQUERYMETRICS)\n\nwith st.sidebar:\n    initial_start_date = datetime.date(2015, 6, 6)\n    selected_date_range = st.date_input(\n        \"Date Range:\",\n        (initial_start_date, initial_start_date + datetime.timedelta(days=7)),\n        format=\"MM.DD.YYYY\",)\n\n    tr_col_l, tr_col_r = st.columns(2)\n    with tr_col_l:\n        selected_start_time_range = st.time_input(\n            \"Start Time Range\",\n            datetime.time(0, 0),\n            key=\"selected_start_time_range\",\n            step=3600,)\n    with tr_col_r:\n        selected_end_time_range = st.time_input(\n            \"End Time Range:\",\n            datetime.time(23, 00),\n            key=\"selected_end_time_range\",\n            step=3600,)\n    h3_options = st.selectbox(\n        \"H3 cells to display\", ([\"All\"] + df_metrics[\"H3\"].to_list()))\n\n    with st.expander(\":orange[Expand to see SMAPE metric]\"):\n        df_metrics_filtered = df_metrics\n        if h3_options != \"All\":\n            df_metrics_filtered = df_metrics[df_metrics[\"H3\"] == h3_options]\n\n        st.dataframe(df_metrics_filtered, hide_index=True, width=300)\n    chckbox_3d_value = st.checkbox(\n        \"3D\", key=\"chkbx_forecast\", help=\"Renders H3 Hexagons in 3D\")\n\nDF_PICKUPS = None\nDF_FORECAST = None\n\nstart_end_date_selected = len(selected_date_range) == 2\n\nif start_end_date_selected:\n    sql_query_pickups = f\"\"\"SELECT h3,\n    SUM(pickups) AS COUNT\n    FROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_compare\n    WHERE pickup_time BETWEEN DATE('{selected_date_range[0]}') AND DATE('{selected_date_range[1]}')\n    AND TIME(pickup_time) BETWEEN '{selected_start_time_range}' AND '{selected_end_time_range}'\n    GROUP BY 1\"\"\"\n\n    sql_query_forecast = f\"\"\"SELECT h3,\n    sum(forecast) AS COUNT\n    FROM ADVANCED_ANALYTICS.PUBLIC.ny_taxi_rides_compare\n    WHERE pickup_time BETWEEN DATE('{selected_date_range[0]}') AND DATE('{selected_date_range[1]}')\n    AND TIME(pickup_time) BETWEEN '{selected_start_time_range}' AND '{selected_end_time_range}'\n    GROUP BY 1\"\"\"\n\n    colors_list = [\"gray\", \"blue\", \"green\", \"yellow\", \"orange\", \"red\"]\n    DF_PICKUPS = get_dataframe_from_raw_sql(sql_query_pickups)\n    quantiles_pickups = DF_PICKUPS[\"COUNT\"].quantile([0, 0.25, 0.5, 0.75, 1])\n    color_map_pickups = generate_linear_color_map(colors_list, quantiles_pickups)\n    DF_PICKUPS[\"COLOR\"] = DF_PICKUPS[\"COUNT\"].apply(color_map_pickups.rgb_bytes_tuple)\n\n    DF_FORECAST = get_dataframe_from_raw_sql(sql_query_forecast)\n    quantiles_forecast = DF_FORECAST[\"COUNT\"].quantile([0, 0.25, 0.5, 0.75, 1])\n    color_map_forecast = generate_linear_color_map(colors_list, quantiles_forecast)\n    DF_FORECAST[\"COLOR\"] = DF_FORECAST[\"COUNT\"].apply(\n        color_map_forecast.rgb_bytes_tuple)\n\n    if h3_options != \"All\":\n        DF_PICKUPS = DF_PICKUPS[DF_PICKUPS[\"H3\"] == h3_options]\n        DF_FORECAST = DF_FORECAST[DF_FORECAST[\"H3\"] == h3_options]\n\ncol1, col2 = st.columns(2)\nwith col1:\n    st.write(\"**Actual Demand**\")\n    pydeck_chart_creation(DF_PICKUPS, avg_coordinate, chckbox_3d_value)\nwith col2:\n    st.write(\"**Forecasted Demand**\")\n    pydeck_chart_creation(DF_FORECAST, avg_coordinate, chckbox_3d_value)\n\ndf_time_series = get_dataframe_from_raw_sql(SQLQUERYTIMESERIES)\nif DF_FORECAST is None or len(DF_FORECAST) == 0:\n    st.stop()\n\ncomparision_df_filter = (\n    (pd.to_datetime(df_time_series[\"PICKUP_TIME\"]).dt.date \u003E= selected_date_range[0])\n    & (pd.to_datetime(df_time_series[\"PICKUP_TIME\"]).dt.date \u003C= selected_date_range[1])\n    & (pd.to_datetime(df_time_series[\"PICKUP_TIME\"]).dt.time \u003E= selected_start_time_range)\n    & (pd.to_datetime(df_time_series[\"PICKUP_TIME\"]).dt.time \u003C= selected_end_time_range))\n\nif h3_options == \"All\":\n    st.markdown(\"### Comparison for All Hexagons\")\n    df_time_series_filtered = (\n        df_time_series[comparision_df_filter]\n        .groupby([\"PICKUP_TIME\"], as_index=False)\n        .sum()\n    )\n    df_time_series_filtered = df_time_series_filtered[\n        [\"PICKUP_TIME\", \"FORECAST\", \"PICKUPS\"]\n    ]\n    with st.expander(\"Raw Data\"):\n        st.dataframe(df_time_series_filtered, use_container_width=True)\nelse:\n    st.markdown(f\"### Comparison for Hexagon ID {h3_options}\")\n    df_time_series_filtered = (\n        df_time_series[(df_time_series[\"H3\"] == h3_options) & comparision_df_filter]\n        .groupby([\"PICKUP_TIME\"], as_index=False)\n        .sum()\n    )\n    with st.expander(\"Raw Data\"):\n        st.dataframe(df_time_series_filtered, use_container_width=True)\n\nrender_plotly_line_chart(df_time_series_filtered)\n```\n\nAfter clicking `Run` button you will see the following UI:\n\n![assets/geo_ml_14.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_14.png)\n\nClick `Expand to see SMAPE metric` in the sidebar and find hexagons with good/bad MAPE values. Find them on the map using `H3 cells to display` dropdown.\n\nAs you can see, overall, the model is quite good, with SMAPE below 0.3 for most of the hexagons. Even with its current quality, the model can already be used to predict future demand. However, let's still consider how you can improve it.\n\nThe worst predictions are for hexagons corresponding to LaGuardia Airport (`882a100e25fffff`, `882a100f57fffff`, `882a100f53fffff`). To address this, you might consider adding information about flight arrivals and departures, which could improve the model's quality. It is a bit surprising to see poor quality at the hexagon `882a100897fffff`, which is close to Central Park. However, it seems that June 7th is the main driver of the poor prediction, as model significantly underpredicted during both day and night hours.\n\n![assets/geo_ml_9.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_9.png)\n\nYou have information about public and school holidays and sports events among our features. Perhaps adding information about other local events, such as festivals, could improve the overall quality of the model.\n\n\u003E \n\u003E  The code from this quickstart can be reused for other industries, such as food delivery, micro-mobility, retail, finance, etc. You might need to use different time intervals, third-party datasets, or quality metrics, but the idea and toolkit stay the same.\n\n## Customer Reviews Sentiment Analysis\n\n\n\u003E \n\u003E  Before starting with this lab, complete the preparation steps from `Setup your account` page.\n\n\u003E \n\u003E  This lab is [available](https://github.com/Snowflake-Labs/sf-guide-geospatial-analytics-ai-ml) as Snowflake Notebook.\n\nThis lab will show you how to inject AI into your spatial analysis using Cortex Large Language Model (LLM) Functions to help you take your product and marketing strategy to the next level. Specifically, you’re going to build a data application that gives food delivery companies the ability to explore the sentiments of customers in the Greater Bay Area. To do this, you use the Cortex LLM Complete Function to classify customer sentiment and extract the underlying reasons for that sentiment from a customer review. Then you use the Discrete [Global Grid H3](https://www.uber.com/en-DE/blog/h3/) for visualizing and exploring spatial data. \n\n### Step 1. Data acquisition\n\nTo complete the project you will use a synthetic dataset with delivery orders with the feedback for each order. We will simplify the task of data acquisition by putting the dataset in an S3 bucket, which you will connect as an external stage.\n\nFirst specify the default Database, Schema and the Warehouse and create a file format that corresponds to the format of the trip and holiday data we stored in S3. Run the following queries:\n```\nUSE ADVANCED_ANALYTICS.PUBLIC;\nUSE WAREHOUSE my_wh;\nCREATE OR REPLACE FILE FORMAT csv_format_nocompression TYPE = csv\nFIELD_OPTIONALLY_ENCLOSED_BY = '\"' FIELD_DELIMITER = ',' skip_header = 1;\n```\nNow you will create an external stage using S3 with test data:\n```\nCREATE OR REPLACE STAGE @ADVANCED_ANALYTICS.PUBLIC.AA_STAGE URL = 's3://sfquickstarts/hol_geo_spatial_ml_using_snowflake_cortex/';\n```\nThen create a table where you will store the customer feedback dataset:\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS AS\nSELECT  $1::NUMBER as order_id,\n        $2::VARCHAR as customer_id,\n        TO_GEOGRAPHY($3) as delivery_location,\n        $4::NUMBER as delivery_postcode,\n        $5::FLOAT as delivery_distance_miles,\n        $6::VARCHAR as restaurant_food_type,\n        TO_GEOGRAPHY($7) as restaurant_location,\n        $8::NUMBER as restaurant_postcode,\n        $9::VARCHAR as restaurant_id,\n        $10::VARCHAR as review\nFROM @ADVANCED_ANALYTICS.PUBLIC.AA_STAGE/food_delivery_reviews.csv (file_format =\u003E 'csv_format_nocompression');\n```\n\nCongratulations!  Now you have `orders_reviews` table containing 100K orders with reviews.\n\n### Step 2. Preparing and running the prompt\n\nIn this step, you will prepare the prompt to run the analysis. For the task at hand, you will use the CORTEX.COMPLETE ( ) function because it is purpose-built to power data processing and data generation tasks. First, let's create a cortex role. In the query below change the username AA to the username you used to login to Snowflake.\n\n```\nCREATE OR REPLACE ROLE cortex_user_role;\nGRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE cortex_user_role;\n\nGRANT ROLE cortex_user_role TO USER AA;\n```\n\nYou are now ready to provide CORTEX.COMPLETE ( ) functions with the instructions on the analysis that you want to produce. Specifically, using a raw table with reviews you'll create a new table with two additional columns: Overall Sentiment and Sentiment Categories which are composed of two different CORTEX.COMPLETE ( ) prompts. For complex aspect-based sentiment analysis like this, you are going to pick the mixtral-8x7b, a very capable open-source LLM created by Mistral AI. \n* **Overall Sentiment** assigns an overall rating of the delivery: Very Positive, Positive, Neutral, Mixed, Negative, Very Negative, or other. \n* **Sentiment Categories** give us richer insights into why the overall rating is based on Food Cost, Quality, and Delivery Time. \n\nAs a general rule when writing a prompt, the instructions have to be simple, clear, and complete. For example, you will notice that you clearly define the task as classifying customer reviews into specific categories. It’s important to define constraints of the desired output, otherwise the LLM will produce unexpected output. Below, you specifically tell the LLM to categorize anything it is not sure of as Other, and explicitly tell it to respond in JSON format. \n\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT_TEST as\nSELECT TOP 10\n    order_id\n    , customer_id\n    , delivery_location\n    , delivery_postcode\n    , delivery_distance_miles\n    , restaurant_food_type\n    , restaurant_location\n    , restaurant_postcode\n    , restaurant_id\n    , review\n    , snowflake.cortex.complete('mixtral-8x7b'\n        , concat('You are a helpful data assistant and your job is to return a JSON formatted response that classifies a customer review (represented in the \u003Creview\u003E section) as one of the following seven sentiment categories (represented in the \u003Ccategories\u003E section). Return your classification exclusively in the JSON format: {classification: \u003C\u003Cvalue\u003E\u003E}, where \u003C\u003Cvalue\u003E\u003E is one of the 7 classification categories in the section \u003Ccategories\u003E. \n        \n        \u003Ccategories\u003E\n        Very Positive\n        Positive\n        Neutral\n        Mixed \n        Negative \n        Very Negative\n        Other\n        \u003C/categories\u003E\n        \n        \"Other\" should be used for the classification if you are unsure of what to put. No other classifications apart from these seven in the \u003Ccategories\u003E section should be used.\n        \n        Here are some examples: \n            1. If review is: \"This place is awesome! The food tastes great, delivery was super fast, and the cost was cheap. Amazing!\", then the output should only be {\"Classification\": \"Very Positive\"}\n            2. If review is: \"Tried this new place and it was a good experience. Good food delivered fast.\", then the output should only be {\"Classification\": \"Positive\"}\n            3. If review is: \"Got food from this new joint. It was OK. Nothing special but nothing to complain about either\", then the output should only be {\"Classification\": \"Neural\"}\n            4. If review is: \"The pizza place we ordered from had the food delivered real quick and it tasted good. It just was pretty expensive for what we got.\", then the output should only be {\"Classification\": \"Mixed\"}\n            5. If review is: \"The hamburgers we ordered took a very long time and when they arrived they were just OK.\", then the output should only be {\"Classification\": \"Negative\"}\n            6. If review is: \"This food delivery experience was super bad. Overpriced, super slow, and the food was not that great. Disappointed.\", then the output should only be {\"Classification\": \"Very Negative\"}\n            7. If review is: \"An experience like none other\", then the output should be \"{\"Classification\": Other\"}\n        \n         It is very important that you do not return anything but the JSON formatted response. \n            \n        \u003Creview\u003E', review, '\u003C/review\u003E\n        JSON formatted Classification Response: '\n                )\n    ) as sentiment_assessment   \n    , snowflake.cortex.complete(\n        'mixtral-8x7b'\n        , concat('You are a helpful data assistant. Your job is to classify customer input \u003Creview\u003E. If you are unsure, return null. For a given category classify the sentiment for that category as: Very Positive, Positive, Mixed, Neutral, Negative, Very Negative. Respond exclusively in JSON format.\n\n        {\n        food_cost:\n        food_quality:\n        food_delivery_time:\n    \n        }\n      '  \n, review \n, 'Return results'\n        )) as sentiment_categories\nFROM \n    ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS;\n```\n\n  If you look inside of `ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT_TEST` you'll notice two new columns: `sentiment_assesment` and `sentiment_categories`. `sentiment_assesment` contains overall assessment of the sentiment based on the review and `sentiment_categories` has an evaluation of each of three components individually: cost, quality and delivery time.\n\n  ![assets/geo_ml_11.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_11.png)\n\n  Now when you see that the results stick to the expected format, you can run the query above without the `top 10` limit. This query might take some time to complete, so to save time for this quickstart we've ran it for you in advance and stored results which you can import into new table by running following two queries:\n\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT (\n\tORDER_ID NUMBER(38,0),\n\tCUSTOMER_ID VARCHAR(16777216),\n\tDELIVERY_LOCATION GEOGRAPHY,\n\tDELIVERY_POSTCODE NUMBER(38,0),\n\tDELIVERY_DISTANCE_MILES FLOAT,\n\tRESTAURANT_FOOD_TYPE VARCHAR(16777216),\n\tRESTAURANT_LOCATION GEOGRAPHY,\n\tRESTAURANT_POSTCODE NUMBER(38,0),\n\tRESTAURANT_ID VARCHAR(16777216),\n\tREVIEW VARCHAR(16777216),\n\tSENTIMENT_ASSESSMENT VARCHAR(16777216),\n\tSENTIMENT_CATEGORIES VARCHAR(16777216)\n);\n\nCOPY INTO ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT\nFROM @ADVANCED_ANALYTICS.PUBLIC.AA_STAGE/food_delivery_reviews.csv\nFILE_FORMAT = (FORMAT_NAME = csv_format_nocompression);\n```\n\n### Step 3. Data transformation\n\nNow when you have a table with sentiment, you need to parse JSONs to store each component of the score into a separate column and convert the scoring provided by the LLM into numeric format, so you can easily visualize it. Run the following query:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT_ANALYSIS AS\nSELECT * exclude (food_cost, food_quality, food_delivery_time, sentiment) ,\n         CASE\n             WHEN sentiment = 'very positive' THEN 5\n             WHEN sentiment = 'positive' THEN 4\n             WHEN sentiment = 'neutral'\n                  OR sentiment = 'mixed' THEN 3\n             WHEN sentiment = 'negative' THEN 2\n             WHEN sentiment = 'very negative' THEN 1\n             ELSE NULL\n         END sentiment_score ,\n         CASE\n             WHEN food_cost = 'very positive' THEN 5\n             WHEN food_cost = 'positive' THEN 4\n             WHEN food_cost = 'neutral'\n                  OR food_cost = 'mixed' THEN 3\n             WHEN food_cost = 'negative' THEN 2\n             WHEN food_cost = 'very negative' THEN 1\n             ELSE NULL\n         END cost_score ,\n         CASE\n             WHEN food_quality = 'very positive' THEN 5\n             WHEN food_quality = 'positive' THEN 4\n             WHEN food_quality = 'neutral'\n                  OR food_quality = 'mixed' THEN 3\n             WHEN food_quality = 'negative' THEN 2\n             WHEN food_quality = 'very negative' THEN 1\n             ELSE NULL\n         END food_quality_score ,\n         CASE\n             WHEN food_delivery_time = 'very positive' THEN 5\n             WHEN food_delivery_time = 'positive' THEN 4\n             WHEN food_delivery_time = 'neutral'\n                  OR food_delivery_time = 'mixed' THEN 3\n             WHEN food_delivery_time = 'negative' THEN 2\n             WHEN food_delivery_time = 'very negative' THEN 1\n             ELSE NULL\n         END delivery_time_score\nFROM\n  (SELECT order_id ,\n          customer_id ,\n          delivery_location ,\n          delivery_postcode ,\n          delivery_distance_miles ,\n          restaurant_food_type ,\n          restaurant_location ,\n          restaurant_postcode ,\n          restaurant_id ,\n          review ,\n          try_parse_json(lower(sentiment_assessment)):classification::varchar AS sentiment ,\n          try_parse_json(lower(sentiment_categories)):food_cost::varchar AS food_cost ,\n          try_parse_json(lower(sentiment_categories)):food_quality::varchar AS food_quality ,\n          try_parse_json(lower(sentiment_categories)):food_delivery_time::varchar AS food_delivery_time\n   FROM ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT);\n```\n\n### Step 4. Data visualization\n\nIn this step, you will visualize the scoring results on the map. Open `Projects \u003E Streamlit \u003E + Streamlit App`. Give the new app a name, for example `Sentiment analysis - results`, and pick `ADVANCED_ANALYTICS.PUBLIC` as an app location.\n\n![assets/geo_ml_12.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_12.png)\n\nClick on the packages tab and add `pydeck` and `branca` to the list of packages as our app will be using them. \n\n![assets/geo_ml_8.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_8.png)\n\nThen copy-paste the following code to the editor and click `Run`:\n\n```\nfrom snowflake.snowpark.context import get_active_session\nfrom typing import Tuple\nimport branca.colormap as cm\nimport pandas as pd\nimport pydeck as pdk\nimport streamlit as st\n\n@st.cache_data\ndef get_dataframe_from_raw_sql(query: str) -\u003E pd.DataFrame:\n    session = get_active_session()\n    pandas_df = session.sql(query).to_pandas()\n    return pandas_df\n\ndef get_h3_df_orders_quantiles(resolution: float, type_of_location: str) -\u003E pd.DataFrame:\n    df = get_dataframe_from_raw_sql(\n        f\"\"\"SELECT\n        H3_POINT_TO_CELL_STRING(to_geography({ type_of_location }), { resolution }) AS h3,\n        round(count(*),2) as count\n        FROM ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT_ANALYSIS\n        GROUP BY 1\"\"\")\n\n    quantiles = get_quantile_in_column(df, \"COUNT\")\n    return df, quantiles\n\ndef get_h3_df_sentiment_quantiles(\n    resolution: float, type_of_sentiment: str, type_of_location: str\n) -\u003E Tuple[pd.DataFrame, pd.core.series.Series]:\n    df = get_dataframe_from_raw_sql(\n        f\"\"\" SELECT \n        H3_POINT_TO_CELL_STRING(TO_GEOGRAPHY({ type_of_location }),{ resolution }) AS h3,\n        round(AVG({ type_of_sentiment }),2) AS count\n        FROM ADVANCED_ANALYTICS.PUBLIC.ORDERS_REVIEWS_SENTIMENT_ANALYSIS\n        WHERE { type_of_sentiment } IS NOT NULL \n        GROUP BY 1\"\"\")\n\n    quantiles = get_quantile_in_column(df, \"COUNT\")\n    df = df[(df[\"COUNT\"] \u003E= values[0]) & (df[\"COUNT\"] \u003C= values[1])]\n    return df, quantiles\n\ndef get_h3_layer(layer_dataframe: pd.DataFrame, elevation_3d: bool = False,) -\u003E pdk.Layer:\n    highest_count_df = 0 if layer_dataframe is None else layer_dataframe[\"COUNT\"].max()\n    return pdk.Layer(\n        \"H3HexagonLayer\",\n        layer_dataframe,\n        get_hexagon=\"H3\",\n        get_fill_color=\"COLOR\",\n        get_line_color=\"COLOR\",\n        auto_highlight=True,\n        get_elevation=f\"COUNT/{highest_count_df}\",\n        elevation_scale=10000 if elevation_3d else 0,\n        elevation_range=[0, 300],\n        pickable=True,\n        opacity=0.5,\n        extruded=True)\n\ndef get_quantile_in_column(\n    quantile_dataframe: pd.DataFrame, column_name: str\n) -\u003E pd.core.series.Series:\n    return quantile_dataframe[column_name].quantile([0, 0.25, 0.5, 0.75, 1])\n\ndef render_pydeck_chart(\n    chart_quantiles: pd.core.series.Series, \n    chart_dataframe: pd.DataFrame, \n    elevation_3d: bool = False):\n    colors = [\"gray\", \"blue\", \"green\", \"yellow\", \"orange\", \"red\"]\n    color_map = cm.LinearColormap(\n        colors,\n        vmin=chart_quantiles.min(),\n        vmax=chart_quantiles.max(),\n        index=chart_quantiles)\n    chart_dataframe[\"COLOR\"] = chart_dataframe[\"COUNT\"].apply(color_map.rgb_bytes_tuple)\n    st.pydeck_chart(\n        pdk.Deck(\n            map_provider=\"mapbox\",\n            map_style=\"light\",\n            initial_view_state=pdk.ViewState(\n                latitude=37.633,\n                longitude=-122.284,\n                zoom=7,\n                pitch=50 if elevation_3d else 0,\n                height=430),\n            tooltip={\"html\": \"\u003Cb\u003EValue:\u003C/b\u003E {COUNT}\",\n            \"style\": {\"color\": \"white\"}},\n            layers=get_h3_layer(chart_dataframe, elevation_3d)))\n\nst.set_page_config(layout=\"centered\", initial_sidebar_state=\"expanded\")\nst.title(\"Reviews of Food Delivery Orders\")\n\nwith st.sidebar:\n    h3_resolution = st.slider(\"H3 resolution\", min_value=6, max_value=9, value=7)\n    type_of_locations = st.selectbox(\"Dimensions\", (\"DELIVERY_LOCATION\", \"RESTAURANT_LOCATION\"), index=0)\n    type_of_data = st.selectbox(\n        \"Measures\",(\"ORDERS\",\"SENTIMENT_SCORE\",\"COST_SCORE\",\"FOOD_QUALITY_SCORE\",\"DELIVERY_TIME_SCORE\"), index=0)\n    if type_of_data != \"ORDERS\":\n        values = st.slider(\"Select a range for score values\", 0.0, 5.0, (0.0, 5.0))\n        chckbox_3d_value = False\n    else:\n        chckbox_3d_value = st.checkbox(\"3D\", key=\"chkbx_forecast\", help=\"Renders H3 Hexagons in 3D\")\n\nif type_of_data != \"ORDERS\":\n    df, quantiles = get_h3_df_sentiment_quantiles(h3_resolution, type_of_data, type_of_locations)\n\nif type_of_data == \"ORDERS\":\n    df, quantiles = get_h3_df_orders_quantiles(h3_resolution, type_of_locations)\n\nst.image(\"https://sfquickstarts.s3.us-west-1.amazonaws.com/hol_geo_spatial_ml_using_snowflake_cortex/gradient.png\")\n\nrender_pydeck_chart(quantiles, df, chckbox_3d_value)\n```\n\nAfter clicking `Run` button you will see the following UI:\n\n![assets/geo_ml_13.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_13.png)\n\nYou can start with the overall analysis of the order density. When you select \"DELIVERY_LOCATION\" as a Dimension and \"ORDERS\" as a Measure you'll see what areas correspond to the high number of orders. You can use scale 7 and zoom in to identify clear clusters of where the most deliveries are occurring. In this case you see most deliveries are in Santa Clara, San Jose, and the San Francisco Bay. In particular, the area on the San Francisco peninsula looks to be an area of interest. Zooming in further you can see a dense area of delivery orders. \n\n![assets/geo_ml_15.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_15.png)\n\nUsing a finer H3 resolution, 8 shows how the delivery densities are distributed more finely. From this resolution, you can see the orders are concentrated in Daly City and proceed down to San Bruno. Additionally, in the North, the majority of the orders are coming from the stretch of the Sunset District to the Mission District.\n\n![assets/geo_ml_16.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_16.png)\n\nNow that you know where the majority of orders are coming from, let's analyze whether there are interesting differences in customer satisfaction depending on where they are located. Select DELIVERY LOCATION as a dimension and SENTIMENT_SCORE as a Measure to see the overall sentiment score that the Cortex LLM Complete Function generated. You can notice that the customers are mostly satisfied in the areas of Daly City down to San Jose, in the Santa Rosa area, and around Dublin. You also see that the area between these is mostly showing unhappy customers.\n\n![assets/geo_ml_17.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_17.png)\n\nIn order to understand why customers in this area are unhappy, you analyze the aspect based sentiment results of the Cortex LLM Complete Function generated  for the categories of interest: food cost, delivery time, and the food quality. If you focus purely on the customers that were unhappy, you see that the primary reasons are food quality and food cost getting poor scores. Essentially, the food is not worth the cost and delivery time being fast does not make up for this. Check visualizations using the following combinations of parameters:\n\n![assets/geo_ml_18.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_18.png)\n\nIf you look at all H3 cells where food quality was high, the average sentiment score is also generally high. Again, you can see there are no cells where customers felt the food quality was above average in the greater Berkeley area. This could indicate either that high quality delivery food is uncommon or that the customers in these areas have higher expectations for delivery food.\n\nYou can also analyze what areas are getting higher scores for each of the categories and how it correlates with the overall sentiment scores for restaurants in each area.\n\n\u003E \n\u003E  The code from this quickstart can be reused for other industries, such as urban mobility, retail, finance, etc. Basically, any industry that involves providing a service with geo components and customer reviews.\n\n## Processing unstructured geospatial data\n\n\n\u003E \n\u003E  Before starting with this lab, complete the preparation steps from `Setup your account` page.\n\n\u003E \n\u003E  This lab is [available](https://github.com/Snowflake-Labs/sf-guide-geospatial-analytics-ai-ml) as Snowflake Notebook.\n\nIn this quickstart guide, we will show you how to read geospatial data from unstructured sources such as GeoTiffs and Shapefiles to prepare features for a machine learning model using Snowflake and popular Python geospatial libraries.\n\nYou will learn how to join data from different sources to help predict the presence of groundwater. Although the prediction step itself is out of scope for this lab, you will learn how to ingest data from raster files and shapefiles and combine them using nearest neighbour approach.\n\nIn this lab, we will use the following sources:\n- Elevation map from [United States Geological Survey](https://www.usgs.gov/).\n- Average precipitation and average temperature data from [WorldClim](https://www.worldclim.org/data/worldclim21.html).\n\nThe result of this lab will be a single dataset containing information derived from the above sources.\n\nSince we will be running relatively complex computations using Snowpark, you will need a `LARGE` Snowpark-optimized warehouse. Run the following query to create one:\n\n```\nCREATE WAREHOUSE IF NOT EXISTS snowpark_opt_wh_l WITH warehouse_size = 'LARGE' warehouse_type = 'SNOWPARK-OPTIMIZED';\n```\n\nNow you can go to notebook settings and set the newly created warehouse as the `SQL warehouse`. Additionally, go to the Packages dropdown and import `branca`, `pydeck` and `rasterio`, which you will use in this lab.\n\n### Step 1. Data Acquisition\nAs a first step, you will attach an external stage with raster and shapefiles. Run the following queries:\n\n```\nCREATE DATABASE IF NOT EXISTS ADVANCED_ANALYTICS;\nCREATE SCHEMA IF NOT EXISTS ADVANCED_ANALYTICS.RASTER;\nUSE SCHEMA ADVANCED_ANALYTICS.RASTER;\n\nCREATE OR REPLACE STAGE ADVANCED_ANALYTICS.RASTER.FILES URL = 's3://sfquickstarts/hol_geo_spatial_ml_using_snowflake_cortex/unstructured/';\n```\n\nFor this lab, you will also use a native application called [SedonaSnow](https://app.snowflake.com/marketplace/listing/GZTYZF0RTY3/wherobots-sedonasnow), which contains more than a hundred geospatial functions.\n\n* Navigate to the `Marketplace` screen using the menu on the left side of the window.\n* Search for `SedonaSnow` in the search bar.\n* Once in the listing, click the big blue `Get` button.\n\n\u003E \n\u003E  On the `Get` screen, you may be prompted to complete your user profile if you have not done so before. Click the link as shown in the screenshot below. Enter your name and email address into the profile screen and click the blue Save button. You will be returned to the `Get` screen.\n\n![assets/geo_ml_22.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_22.png)\n\nCongratulations, you have now acquired all the data sources that you need for this lab.\n\n### Step 2. Loading Raster Data\nIn this step, you will load data from raster files stored in an external stage and store it as a Snowflake table.\n\nYou will start with elevation data. Let's first create a function that uses the Python library `rasterio`, available in the [Snowflake Conda Channel](https://repo.anaconda.com/pkgs/snowflake/), which reads metadata from a `GeoTiff` file stored in a stage. Run the following query:\n\n```\nCREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_EXTRACT_GEOTIFF_METADATA(PATH_TO_FILE STRING)\nRETURNS TABLE (\n    status BOOLEAN,\n    error STRING,\n    band_count INT,\n    crs STRING,\n    bounds STRING,\n    metadata STRING\n)\nLANGUAGE PYTHON\nRUNTIME_VERSION = '3.8'\nPACKAGES = ('rasterio', 'snowflake-snowpark-python')\nHANDLER = 'GeoTiffMetadataExtractor'\nAS $$\nimport rasterio\nimport json\nfrom snowflake.snowpark.files import SnowflakeFile\n\nclass GeoTiffMetadataExtractor:\n    def process(self, PATH_TO_FILE: str):\n        try:\n            # Initialize the result variables\n            status = False\n            error = ''\n            band_count = None\n            crs = None\n            bounds_json = None\n            metadata_json = None\n\n            # Read the GeoTIFF file from the specified stage path into memory\n            with SnowflakeFile.open(PATH_TO_FILE, 'rb', require_scoped_url=False) as input_file:\n                tif_bytes = input_file.read()\n\n            # Use rasterio's MemoryFile to read the TIFF data from memory\n            with rasterio.MemoryFile(tif_bytes) as memfile:\n                with memfile.open() as dataset:\n                    # Extract metadata from the dataset\n                    band_count = dataset.count\n                    crs = str(dataset.crs)  # Convert CRS to string for serialization\n                    bounds = dataset.bounds._asdict()  # Convert bounds to a dictionary\n\n                    # Ensure that metadata is serializable\n                    metadata = dataset.meta.copy()\n                    # Convert 'transform' to a tuple\n                    if 'transform' in metadata:\n                        metadata['transform'] = metadata['transform'].to_gdal()\n                    # Convert 'crs' to string\n                    if 'crs' in metadata:\n                        metadata['crs'] = str(metadata['crs'])\n\n                    # Convert bounds and metadata to JSON strings\n                    bounds_json = json.dumps(bounds)\n                    metadata_json = json.dumps(metadata)\n\n                    # Parsing successful\n                    status = True\n\n        except Exception as e:\n            # Handle exceptions, such as corrupted files\n            error = str(e)\n            status = False\n\n        # Yield the result as a single row\n        yield (\n            status,\n            error,\n            band_count,\n            crs,\n            bounds_json,\n            metadata_json\n        )\n$$;\n```\n\nAdditionally, you will create a function to check the distribution of bands. Sometimes, bands of certain values prevail over others, and stripping them off during loading of data from raster files can significantly reduce the size of the table.\n\n```\nCREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_RASTER_BAND_VALUE_STATS(PATH_TO_FILE STRING)\nRETURNS TABLE (\n    band_value FLOAT,\n    count BIGINT,\n    percentage FLOAT\n)\nLANGUAGE PYTHON\nRUNTIME_VERSION = '3.8'\nPACKAGES = ('numpy', 'rasterio', 'snowflake-snowpark-python')\nHANDLER = 'RasterBandValueStats'\nAS $$\nimport numpy as np\nimport rasterio\nfrom snowflake.snowpark.files import SnowflakeFile\n\nclass RasterBandValueStats:\n    def process(self, PATH_TO_FILE: str):\n        try:\n            # Read the GeoTIFF file from the specified stage path\n            with SnowflakeFile.open(PATH_TO_FILE, 'rb', require_scoped_url=False) as input_file:\n                tif_bytes = input_file.read()  # Read the entire file into bytes\n\n            # Use rasterio's MemoryFile to read the TIFF data from memory\n            with rasterio.MemoryFile(tif_bytes) as memfile:\n                with memfile.open() as dataset:\n                    # Read all bands into a NumPy array\n                    data = dataset.read()  # Shape: (band_count, rows, cols)\n\n                    # Flatten the data across all bands\n                    data_flat = data.flatten()  # 1D array of all pixel values across all bands\n\n                    # Count unique values\n                    unique_values, counts = np.unique(data_flat, return_counts=True)\n\n                    # Calculate total number of values\n                    total_count = data_flat.size\n\n                    # Calculate percentage for each unique value\n                    percentages = (counts / total_count) * 100\n\n                    # Yield results\n                    for value, count, percentage in zip(unique_values, counts, percentages):\n                        yield (\n                            float(value),\n                            int(count),\n                            round(float(percentage),1)\n                        )\n        except Exception as e:\n            raise Exception(f\"Error during data extraction: {e}\")\n$$;\n```\n\nNext, you will create a function that reads data from a `GeoTIFF` file and outputs it as a table. The UDF you will create processes each pixel in the `GeoTIFF` file by calculating the spatial coordinates (X and Y) of the centroid of the pixel. It then associates these coordinates with the pixel's corresponding band values.\n\nThis approach transforms the raster image into a collection of spatial points enriched with attribute data (band values), making it suitable for vector-based analyses and database operations. Run the following query:\n\n```\nCREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOTIFF(\n    PATH_TO_FILE STRING,\n    SKIP_VALUES ARRAY DEFAULT NULL  -- Make SKIP_VALUES optional with default NULL\n)\nRETURNS TABLE (\n    x FLOAT,\n    y FLOAT,\n    band_values ARRAY,\n    band_count INT\n)\nLANGUAGE PYTHON\nRUNTIME_VERSION = '3.8'\nPACKAGES = ('numpy', 'rasterio', 'snowflake-snowpark-python')\nHANDLER = 'GeoTiffExtractor'\nAS $$\nimport numpy as np\nimport rasterio\nfrom snowflake.snowpark.files import SnowflakeFile\n\nclass GeoTiffExtractor:\n    def process(self, PATH_TO_FILE: str, SKIP_VALUES=None):\n        try:\n            # Read the GeoTIFF file from the specified stage path\n            with SnowflakeFile.open(PATH_TO_FILE, 'rb', require_scoped_url=False) as input_file:\n                tif_bytes = input_file.read()  # Read the entire file into bytes\n\n            # Use rasterio's MemoryFile to read the TIFF data from memory\n            with rasterio.MemoryFile(tif_bytes) as memfile:\n                with memfile.open() as dataset:\n                    # Read all bands into a NumPy array\n                    data = dataset.read()  # Shape: (band_count, rows, cols)\n\n                    # Get the number of bands\n                    band_count = data.shape[0]\n\n                    # Get the coordinates\n                    rows, cols = np.indices((dataset.height, dataset.width))\n                    xs, ys = rasterio.transform.xy(\n                        dataset.transform, rows, cols, offset='center'\n                    )\n\n                    # Flatten the arrays\n                    xs = np.array(xs).flatten()\n                    ys = np.array(ys).flatten()\n                    pixel_values = data.reshape((band_count, -1)).T  # Shape: (num_pixels, band_count)\n\n                    # Handle SKIP_VALUES\n                    if SKIP_VALUES:\n                        # Convert SKIP_VALUES to a NumPy array for efficient comparison\n                        skip_values = np.array(SKIP_VALUES)\n\n                        # Create a mask for pixels to skip\n                        skip_mask = np.isin(pixel_values, skip_values).any(axis=1)\n                        # Invert the skip_mask to get the mask of pixels to keep\n                        mask = ~skip_mask\n\n                        # Apply the mask to xs, ys, and pixel_values\n                        xs_filtered = xs[mask]\n                        ys_filtered = ys[mask]\n                        pixel_values_filtered = pixel_values[mask]\n                    else:\n                        # If SKIP_VALUES not provided, use all data\n                        xs_filtered = xs\n                        ys_filtered = ys\n                        pixel_values_filtered = pixel_values\n\n                    # For each pixel, yield a row with x, y, and band values\n                    for i in range(len(xs_filtered)):\n                        # Get the pixel values for all bands\n                        band_vals = pixel_values_filtered[i].tolist()\n                        yield (\n                            xs_filtered[i],\n                            ys_filtered[i],\n                            band_vals,\n                            band_count\n                        )\n        except Exception as e:\n            raise Exception(f\"Error during data extraction: {e}\")\n$$;\n```\n\nNow you will check the metadata of the elevation file. Run the following query:\n\n```\nSELECT *\nFROM table(PY_EXTRACT_GEOTIFF_METADATA(build_scoped_file_url(@FILES,'ASTGTMV003_N07E033_dem.tif')));\n```\n\nAs you can see, the GeoTiff uses reference system `EPSG:4326` which means you can store it as `GEOGRAPHY` type. Run the following query to check how bands are distributed inside of the raster.\n\n```\nSELECT *\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_RASTER_BAND_VALUE_STATS(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'ASTGTMV003_N07E033_dem.tif')));\n```\n\nThere are no obvious outliers among band values—those that correspond to most of the raster points. In this case, let's load data from the whole `ASTGTMV003_N07E033_dem.tif` into the table `POC.RASTER.AFRICA_ELEVATION`:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION AS\nSELECT st_makepoint(x, y) as geog,\nband_values[0]::float as band\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOTIFF(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'ASTGTMV003_N07E033_dem.tif')));\n```\n\nLet's check the size of the newly created table. Run the following query:\n\n```\nSELECT count(*) FROM ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION\n```\n\n12,967,201 rows. The number of rows is a product of width and height in pixels. In the case of the elevation file, it's 3601×3601. Some raster files might be quite large, and to process them, it might be a good idea to use Snowpark-optimized warehouses to avoid memory exhaustion issues. Another technique that you could apply is to load not all points from the raster file but only those that contain useful information. Alternatively, you can resample large files to reduce their resolution. We will show you an example of how to do this at the end of this lab.\n\nBut 13M rows is also a rather large table, and visualizing its results using Python libraries might be challenging without reducing the number of rows. H3 functions can help with that. In the code below, you will do the following:\n\n- Map each point from `POC.RASTER.AFRICA_ELEVATION` to an H3 cell with resolution 8.\n- Group by H3 Cell ID and calculate the average value of the band for each H3 cell.\n- Visualize the H3 cells, using the band as the source for color coding.\n\n```\nimport streamlit as st\nimport pandas as pd\nimport pydeck as pdk\nfrom typing import List\nimport branca.colormap as cm\nfrom snowflake.snowpark.context import get_active_session\n\nsession = get_active_session()\n\n# Execute the updated SQL query\ndf = session.sql('''select h3_point_to_cell_string(geog, 8) as h3_cell,\n                    st_x(h3_cell_to_point(h3_cell)) as lon,\n                    st_y(h3_cell_to_point(h3_cell)) as lat,\n                    avg(band) as band \n                    from ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION\n                    group by all;''').to_pandas()\n\ndf[\"BAND\"] = df[\"BAND\"].apply(lambda row: float(row))\ncenter_latitude = df['LAT'].mean()\ncenter_longitude = df['LON'].mean()\n\ndef get_quantiles(df_column: pd.Series, quantiles: List) -\u003E pd.Series:\n    return df_column.quantile(quantiles)\n\ndef get_color(df_column: pd.Series, colors: List, vmin: int, vmax: int, index: pd.Series) -\u003E pd.Series:\n    color_map = cm.LinearColormap(colors, vmin=vmin, vmax=vmax, index=index)\n    return df_column.apply(color_map.rgb_bytes_tuple)\n    \nquantiles = get_quantiles(df[\"BAND\"], [0, 0.2, 0.4, 0.6, 0.8, 1])\ncolors = ['gray','blue','green','yellow','orange','red']\n\ndf['BAND'] = get_color(df['BAND'], colors, quantiles.min(), quantiles.max(), quantiles)\n\nst.pydeck_chart(pdk.Deck(\n    map_style=None,\n    initial_view_state=pdk.ViewState(\n        latitude=center_latitude,\n        longitude=center_longitude, \n        zoom=8.7, \n        bearing=0, \n        pitch=0),\n    layers=[\n        pdk.Layer(\n            \"H3HexagonLayer\",\n            df,\n            opacity=0.9,\n            stroked=False,\n            get_hexagon=\"H3_CELL\",\n            get_fill_color='BAND',\n            extruded=False,\n            wireframe=True,\n            line_width_min_pixels=0,\n            auto_highlight=True,\n            pickable=False,\n            filled=True\n        )\n    ],\n))\n```\n\n![assets/geo_ml_23.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_23.png)\n\n### Step 3. Load Shapefile\n\nIn this step you will load precipitation and average temperature data from a Shapefile. First, you will create a UDF that uses [Dynamic file Access](https://docs.snowflake.com/en/developer-guide/udf/python/udf-python-examples#reading-a-dynamically-specified-file-with-snowflakefile) and `fiona` library to read metadata from Shapefile. Run the following code:\n\n```\nCREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOFILE_METADATA(PATH_TO_FILE string, filename string)\nRETURNS TABLE (metadata variant)\nLANGUAGE python\nRUNTIME_VERSION = 3.8\nPACKAGES = ('fiona', 'snowflake-snowpark-python')\nHANDLER = 'GeoFileReader'\nAS $$\n# Import necessary modules for file handling and geospatial data processing\nfrom snowflake.snowpark.files import SnowflakeFile\nfrom fiona.io import ZipMemoryFile\nimport fiona\n\n# Helper function to make objects JSON-serializable\ndef make_serializable(obj):\n    if isinstance(obj, dict):\n        # Recursively process dictionary items\n        return {k: make_serializable(v) for k, v in obj.items()}\n    elif isinstance(obj, (list, tuple)):\n        # Recursively process lists and tuples\n        return [make_serializable(v) for v in obj]\n    elif isinstance(obj, (int, float, str, bool, type(None))):\n        # Base case: object is already serializable\n        return obj\n    else:\n        # Convert non-serializable objects to strings\n        return str(obj)\n\n# Define the handler class for the UDF\nclass GeoFileReader:\n    def process(self, PATH_TO_FILE: str, filename: str):\n        # Enable support for KML drivers in Fiona\n        fiona.drvsupport.supported_drivers['libkml'] = 'rw'\n        fiona.drvsupport.supported_drivers['LIBKML'] = 'rw'\n\n        # Open the file from the Snowflake stage in binary read mode\n        with SnowflakeFile.open(PATH_TO_FILE, 'rb') as f:\n            # Read the zip file into memory using Fiona's ZipMemoryFile\n            with ZipMemoryFile(f) as zip:\n                # Open the specified file within the zip archive\n                with zip.open(filename) as collection:\n                    # Extract metadata from the collection\n                    metadata = {\n                        'driver': collection.driver,  # File format driver (e.g., 'ESRI Shapefile')\n                        'crs': collection.crs.to_string() if collection.crs else None,  \n                        'schema': collection.schema,  # Schema of the data (fields and types)\n                        'bounds': collection.bounds,  # Spatial bounds of the dataset\n                        'meta': collection.meta,      # Additional metadata\n                        'name': collection.name,      # Name of the collection\n                        'encoding': collection.encoding,  # Character encoding of the file\n                        'length': len(collection),    # Number of features in the dataset\n                    }\n                    # Ensure the metadata is serializable to JSON\n                    serializable_metadata = make_serializable(metadata)\n                    # Yield the metadata as a tuple (required for UDFs returning TABLE)\n                    yield (serializable_metadata,)\n$$;\n```\n\nThe UDF above can be used not only to read metadata from Shapefiles but also from other types of geo files, such as KML. You will need another UDF for reading data from geo formats, including Shapefiles. To create one, run the following query:\n\n```\nCREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOFILE(PATH_TO_FILE string, filename string)\nRETURNS TABLE (wkt string, properties object)\nLANGUAGE python\nRUNTIME_VERSION = 3.8\nPACKAGES = ('fiona', 'shapely', 'snowflake-snowpark-python')\nHANDLER = 'GeoFileReader'\nAS $$\n# Import necessary modules for geometry handling and file operations\nfrom shapely.geometry import shape\nfrom snowflake.snowpark.files import SnowflakeFile\nfrom fiona.io import ZipMemoryFile\nimport fiona\n\n# Define the handler class for the UDF\nclass GeoFileReader:\n    def process(self, PATH_TO_FILE: str, filename: str):\n        # Enable support for KML drivers in Fiona\n        fiona.drvsupport.supported_drivers['libkml'] = 'rw'\n        fiona.drvsupport.supported_drivers['LIBKML'] = 'rw'\n\n        # Open the file from the Snowflake stage in binary read mode\n        with SnowflakeFile.open(PATH_TO_FILE, 'rb') as f:\n            # Read the zip file into memory using Fiona's ZipMemoryFile\n            with ZipMemoryFile(f) as zip:\n                # Open the specified geospatial file within the zip archive\n                with zip.open(filename) as collection:\n                    # Iterate over each feature (record) in the collection\n                    for record in collection:\n                        # Check if the geometry is not None\n                        if record['geometry'] is not None:\n                            # Convert the geometry to Well-Known Text (WKT) format\n                            wkt = shape(record['geometry']).wkt\n                            # Convert the properties to a dictionary\n                            properties = dict(record['properties'])\n                            # Yield the WKT and properties as a tuple\n                            yield (wkt, properties)\n$$;\n```\n\nNow you can look into the metadata of `WorldClim.shp` stored in `WorldClim.zip` package:\n\n```\nSELECT parse_json(metadata):crs as metadata\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOFILE_METADATA(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'WorldClim.zip'), 'WorldClim.shp'));\n```\n\nIt stores spatial objects using the Spatial Reference System `EPSG:4326`. You can examine the Shapefile to check its structure:\n\n```\nSELECT top 10 *\nFROM table(PY_LOAD_GEOFILE(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'WorldClim.zip'), 'WorldClim.shp'));\n```\n\n![assets/geo_ml_24.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_24.png)\n\nIt stores geo objects in the `WKT` column and precipitation (`PREC`) and average temperature (`TAVG`) as properties in a JSON-like object. Knowing this information, it's easy to create a query that reads data from a Shapefile and stores it in a table. This is what you will do in the following query:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.WORLDWIDE_WEATHER AS\nSELECT to_geography(wkt) as geog, \nproperties:\"PREC\"::float as prec, \nproperties:\"TAVG\"::float as tavg\nFROM table(PY_LOAD_GEOFILE(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'WorldClim.zip'), 'WorldClim.shp'));\n```\n\nNow that you have a table with average temperature and precipitation, you can visualize it using `pydeck`. Since the newly created table `ADVANCED_ANALYTICS.RASTER.WORLDWIDE_WEATHER` contains more than 800K rows, you may want to reduce its size. In the Python code below, you will do the following:\n- Read data from the weather table, group it using H3 cells of resolution 3, and calculate the average temperature value for each cell.\n- Get the GeoJSON of H3 cell centroids and pass it to a Pandas DataFrame.\n- Extract the longitude and latitude coordinates from the GeoJSON data to prepare it for visualization.\n- Define a color map and assign a color to each data point based on where its value falls within the quantiles.\n- Finally, create a `pydeck` scatterplot layer using the processed data.\nOf course, you could also visualize data using H3 cells as you've done before, but to demonstrate different visualization approaches, you will use points with a radius of 50 km. You can replace `avg(tavg)` with `avg(prec)` to visualize precipitation instead of average temperature.\n\n```\nimport streamlit as st\nimport pandas as pd\nimport numpy as np\nimport pydeck as pdk\nimport json\nfrom typing import List\nimport branca.colormap as cm\nfrom snowflake.snowpark.context import get_active_session\n\nsession = get_active_session()\ndf = session.sql('''select st_asgeojson(h3_cell_to_point(h3_point_to_cell(geog, 3))) as geog, \n                    avg(tavg) as value from ADVANCED_ANALYTICS.RASTER.WORLDWIDE_WEATHER\n                    group by all;''').to_pandas()\n\ndf[\"lon\"] = df[\"GEOG\"].apply(lambda row: json.loads(row)[\"coordinates\"][0])\ndf[\"lat\"] = df[\"GEOG\"].apply(lambda row: json.loads(row)[\"coordinates\"][1])\n\n\ndf[\"VALUE\"] = df[\"VALUE\"].apply(lambda row: float(row))\ncenter_latitude = df['lat'].mean()\ncenter_longitude = df['lon'].mean()\n\ndef get_quantiles(df_column: pd.Series, quantiles: List) -\u003E pd.Series:\n    return df_column.quantile(quantiles)\n\ndef get_color(df_column: pd.Series, colors: List, vmin: int, vmax: int, index: pd.Series) -\u003E pd.Series:\n    color_map = cm.LinearColormap(colors, vmin=vmin, vmax=vmax, index=index)\n    return df_column.apply(color_map.rgb_bytes_tuple)\n\nquantiles = get_quantiles(df[\"VALUE\"], [0, 0.2, 0.4, 0.6, 0.8, 1])\ncolors = ['gray','blue','green','yellow','orange','red']\n\ndf['COLOR'] = get_color(df['VALUE'], colors, quantiles.min(), quantiles.max(), quantiles)\n\nst.pydeck_chart(pdk.Deck(\n    map_style=None,\n    initial_view_state=pdk.ViewState(\n        latitude=center_latitude,\n        longitude=center_longitude, pitch=0, zoom=0\n    ),\n    layers=[\n        pdk.Layer(\n            \"ScatterplotLayer\",\n            data=df,\n            get_position=[\"lon\", \"lat\"],\n            opacity=0.9,\n            stroked=True,\n            filled=True,\n            extruded=True,\n            wireframe=True,\n            get_color='COLOR',\n            get_fill_color='COLOR',\n            get_radius=\"50000\",\n            auto_highlight=True,\n            pickable=False,\n        )\n    ],\n))\n```\n\n![assets/geo_ml_25.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_25.png)\n\nNow that you have all the data from GeoTiff and Shapefile stored in Snowflake tables, you can join them.\n\n### Step 4. Joining Data from Different Sources\nIn this step, you will join data from two datasets. Let's start by joining the `Elevation` and `Weather` datasets. We observed in the visualizations above that the Elevation dataset covers a relatively small area in Africa, whereas the Weather dataset covers the whole world. To speed up joining these datasets, we can remove from the Weather dataset all points that are outside our area of interest, which corresponds to the coverage area of the Elevation dataset.\n\nIn the query below, you will do the following:\n\n- Use native `ST_` functions to get the minimum and maximum boundaries of the Elevation dataset and create a polygon that corresponds to its outer boundaries.\n- Use the SedonaSnow [ST_Buffer](https://sedona.apache.org/1.5.1/api/snowflake/vector-data/Function/#st_buffer) function to extend that boundary by 0.5 degrees in all directions.\n- Filter out from the Weather dataset all points that are outside the boundaries created in the previous step.\n- Create the `ADVANCED_ANALYTICS.RASTER.WORLDWIDE_WEATHER` table with the new results.\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER AS\nwith boundary as (SELECT min(st_xmin(geog)) as xmin, \nmax(st_xmax(geog)) as xmax,\nmin(st_ymin(geog)) as ymin,\nmax(st_ymax(geog)) as ymax,\nsedonasnow.sedona.st_buffer(to_geography('POLYGON ((' || xmin || ' ' || ymin || ', ' ||\n       \t\t\t       xmin || ' ' || ymax || ', ' ||\n       \t\t\t       xmax || ' ' || ymax || ', ' ||\n       \t\t\t       xmax || ' ' || ymin || ', ' ||\n       \t\t\t       xmin || ' ' || ymin ||'))'), 0.5) as external_boundary\nFROM ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION)\nSELECT *\nFROM ADVANCED_ANALYTICS.RASTER.WORLDWIDE_WEATHER,\nboundary\nWHERE ST_INTERSECTS(external_boundary, geog)\n```\n\nNow chech how many points contains the new dataset:\n\n```\nSELECT COUNT(*) FROM ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER\n```\n\nOne hundred forty. As a next step, you need to join the `AFRICA_ELEVATION` table with `AFRICA_WEATHER`. Our goal is to find, for each point in the elevation dataset, the closest point from the weather dataset to get weather information. We can do this using different approaches. One approach would be to calculate nearest neighbours using an `ST_DWITHIN`-based join. In the query below, you join two datasets using points that are within 200 km of each other, then partition by objects in `AFRICA_ELEVATION` and, in each partition, sort by distance and keep only the first element:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.ELEVATION_WEATHER_NN AS\nSELECT t1.geog,\n       t1.band,\n       t2.prec,\n       t2.tavg\nFROM ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION t1,\n     ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER t2,\nWHERE ST_DWITHIN(t1.geog, t2.geog, 200000) QUALIFY ROW_NUMBER() OVER \n(PARTITION BY st_aswkb(t1.geog) ORDER BY ST_DISTANCE(t1.geog, t2.geog)) \u003C= 1;\n```\n\nIt took more than 5 minutes on a `LARGE` warehouse. The problem with the query above is that, as a result of the join, it creates an internal table with 1.8 billion rows (12,967,201 × 140), which makes it quite a complex join.\n\nLet's try another approach, which will include two steps:\n\n- For the `AFRICA_WEATHER` table, create a table with Voronoi polygons.\n- Join `AFRICA_ELEVATION` and `AFRICA_WEATHER` using Voronoi polygons and `ST_WITHIN` instead of `ST_DWITHIN`.\n\nA Voronoi polygon is essentially a region consisting of all points closer to a specific seed point than to any other seed point, effectively partitioning space into cells around each seed. So when you join `AFRICA_ELEVATION` with `AFRICA_WEATHER` using points from the elevation table and Voronoi polygons from the weather table, you can be sure that for each elevation point, you are associating it with its nearest weather data.\n\nTo build Voronoi polygons, you will use the [ST_VORONOIPOLYGONS](https://sedona.apache.org/1.5.1/api/snowflake/vector-data/Function/#st_voronoipolygons) function from the [SedonaSnow](https://app.snowflake.com/marketplace/listing/GZTYZF0RTY3/wherobots-ai-sedonasnow) native app. It takes a multi-object as input—in our case, a Multipoint—and returns Voronoi polygons for that object. Since it returns all polygons also as one object, we need a function that converts a multipolygon into multiple separate polygons. Run the following query to create such a UDF:\n\n```\nCREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.ST_GETPOLYGONS(G OBJECT)\nRETURNS TABLE (POLYGON OBJECT)\nLANGUAGE JAVASCRIPT\nAS '\n{\nprocessRow: function split_multipolygon(row, rowWriter, context){\n    let geojson = row.G;\n    let polygons = [];\n    \n    function extractPolygons(geometry) {\n        if (geometry.type === \"Polygon\") {\n            polygons.push(geometry.coordinates);\n        } else if (geometry.type === \"MultiPolygon\") {\n            for (let i = 0; i \u003C geometry.coordinates.length; i++) {\n                polygons.push(geometry.coordinates[i]);\n            }\n        } else if (geometry.type === \"GeometryCollection\") {\n            for (let i = 0; i \u003C geometry.geometries.length; i++) {\n                extractPolygons(geometry.geometries[i]);\n            }\n        }\n        // Ignore other geometry types (e.g., Point, LineString)\n    }\n    \n    extractPolygons(geojson);\n    \n    for (let i = 0; i \u003C polygons.length; i++) {\n        rowWriter.writeRow({POLYGON: {\n                \"type\" : \"Polygon\",\n                \"coordinates\": polygons[i]\n            }\n        });\n    }\n}\n}\n';\n```\n\nIn the next query, you build Voronoi polygons for the weather table and enrich the `AFRICA_WEATHER` table with those polygons:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER AS\n-- voronoi_grid CTE that stores all points from AFRICA_WEATHER table \n-- into one object and creates Voronoi Polygons\nwith voronoi_grid as (SELECT sedonasnow.sedona.ST_VoronoiPolygons(st_union_agg(geog)) as polygons\nfrom ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER),\n-- CTE that flattens results of voronoi_grid CTE\nvoronoi_grid_flattened as (select to_geography(polygon) as polygon\nfrom voronoi_grid,\ntable(ADVANCED_ANALYTICS.RASTER.ST_GETPOLYGONS(st_asgeojson(polygons))))\n-- Below you join table with voronoi polygons and table with weather information\nSELECT *\nFROM ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER\nINNER JOIN voronoi_grid_flattened\nON ST_WITHIN(geog, polygon);\n```\n\nNow when you have voronoi polygons in `AFRICA_WEATHER` table, you can join `AFRICA_ELEVATION` and `AFRICA_WEATHER`. Run the following query:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.ELEVATION_WEATHER_VORONOI AS\nselect t1.geog, band, prec, tavg\nfrom ADVANCED_ANALYTICS.RASTER.AFRICA_ELEVATION t1\nINNER JOIN ADVANCED_ANALYTICS.RASTER.AFRICA_WEATHER t2\nON ST_INTERSECTS(t1.geog, t2.polygon)\n```\n\nLet's look inside of the newly created table:\n\n```\nSELECT TOP 5 * FROM ADVANCED_ANALYTICS.RASTER.ELEVATION_WEATHER_VORONOI\n```\n\n![assets/geo_ml_26.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_26.png)\n\nNow you have data from two unstructured sources stored in a single table. You can use this table to feed into an ML model or enrich it further with some additional features.\n\n### Advanced Raster Use Case\nSometimes raster files can be really large, but as we mentioned earlier, often most of the points contain no data or some default values. As an example, let's look at the raster file from [Forrest Data Lab](https://skogsdatalabbet.se/services/) (`Skogsdatalabbets filserver vid SLU` › `SLU_Forest_Map` › `Tradslag`). `Bok_andel.tif` is 149 MB in size and has a resolution of 52,600×123,200, which results in about 6.5 billion points. Loading all those points would be quite an expensive step, but let's check how band values are distributed inside of that file. Run the following query:\n\n```\nSELECT *\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_RASTER_BAND_VALUE_STATS(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'Bok_andel.tif')));\n```\n\nYou see that the most frequent band value is 0, which corresponds to 99% of the points. If we load data without those points, we probably won't lose any useful information, but we can have a good saving on compute. Additionally, you can resample the raster to reduce its size. Create a UDF that does both - it resamples to reduce the initial file to the given number of points (50M by default) and ignores given band values:\n\n```\nCREATE OR REPLACE FUNCTION ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOTIFF_RESAMPLE_SKIP(\n    PATH_TO_FILE STRING,\n    SKIP_VALUES ARRAY DEFAULT NULL,   -- Optional SKIP_VALUES parameter\n    MAX_PIXELS INT DEFAULT 50000000   -- New optional MAX_PIXELS parameter with default value\n)\nRETURNS TABLE (\n    x FLOAT,\n    y FLOAT,\n    band_values ARRAY,\n    band_count INT\n)\nLANGUAGE PYTHON\nRUNTIME_VERSION = '3.8'\nPACKAGES = ('numpy', 'rasterio', 'snowflake-snowpark-python')\nHANDLER = 'GeoTiffExtractor'\nAS $$\nimport numpy as np\nimport rasterio\nfrom rasterio.enums import Resampling\nfrom snowflake.snowpark.files import SnowflakeFile\nimport math\n\nclass GeoTiffExtractor:\n    def process(self, PATH_TO_FILE: str, SKIP_VALUES=None, MAX_PIXELS=500000000):\n        try:\n            # Read the GeoTIFF file from the specified stage path\n            with SnowflakeFile.open(PATH_TO_FILE, 'rb', require_scoped_url=False) as input_file:\n                tif_bytes = input_file.read()  # Read the entire file into bytes\n\n            # Use rasterio's MemoryFile to read the TIFF data from memory\n            with rasterio.MemoryFile(tif_bytes) as memfile:\n                with memfile.open() as dataset:\n                    # Get the original dimensions\n                    height = dataset.height\n                    width = dataset.width\n\n                    total_pixels = height * width\n\n                    if total_pixels \u003E MAX_PIXELS:\n                        # Calculate scaling factor\n                        scaling_factor = math.sqrt(MAX_PIXELS / total_pixels)\n                        new_height = int(height * scaling_factor)\n                        new_width = int(width * scaling_factor)\n\n                        # Read the data with the new dimensions\n                        data = dataset.read(\n                            out_shape=(\n                                dataset.count,\n                                new_height,\n                                new_width\n                            ),\n                            resampling=Resampling.average\n                        )\n\n                        # Update the transform for the new dimensions\n                        transform = dataset.transform * dataset.transform.scale(\n                            (width / new_width),\n                            (height / new_height)\n                        )\n                    else:\n                        # Read all bands into a NumPy array\n                        data = dataset.read()  # Shape: (band_count, rows, cols)\n                        transform = dataset.transform\n                        new_height = height\n                        new_width = width\n\n                    # Get the number of bands\n                    band_count = data.shape[0]\n\n                    # Get the coordinates\n                    rows, cols = np.indices((new_height, new_width))\n                    xs, ys = rasterio.transform.xy(\n                        transform, rows, cols, offset='center'\n                    )\n\n                    # Flatten the arrays\n                    xs = np.array(xs).flatten()\n                    ys = np.array(ys).flatten()\n                    pixel_values = data.reshape((band_count, -1)).T  # Shape: (num_pixels, band_count)\n\n                    # Handle SKIP_VALUES\n                    if SKIP_VALUES:\n                        # Convert SKIP_VALUES to a NumPy array for efficient comparison\n                        skip_values = np.array(SKIP_VALUES)\n\n                        # Create a mask for pixels to skip\n                        skip_mask = np.isin(pixel_values, skip_values).any(axis=1)\n                        # Invert the skip_mask to get the mask of pixels to keep\n                        mask = ~skip_mask\n\n                        # Apply the mask to xs, ys, and pixel_values\n                        xs_filtered = xs[mask]\n                        ys_filtered = ys[mask]\n                        pixel_values_filtered = pixel_values[mask]\n                    else:\n                        # If SKIP_VALUES not provided, use all data\n                        xs_filtered = xs\n                        ys_filtered = ys\n                        pixel_values_filtered = pixel_values\n\n                    # For each pixel, yield a row with x, y, and band values\n                    for i in range(len(xs_filtered)):\n                        # Get the pixel values for all bands\n                        band_vals = pixel_values_filtered[i].tolist()\n                        yield (\n                            xs_filtered[i],\n                            ys_filtered[i],\n                            band_vals,\n                            band_count\n                        )\n        except Exception as e:\n            raise Exception(f\"Error during data extraction: {e}\")\n$$;\n```\n\nNow you can load data from `Bok_andel.tif`. Run the query below to reduce the size of the initial file to 500 million points and ignore points where the band value equals zero.\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.BOK_ANDEL AS\nSELECT x, y,\nband_values[0]::float as band\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_LOAD_GEOTIFF_RESAMPLE_SKIP(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'Bok_andel.tif'), [0], 500000000));\n```\n\nIn the prevous query you stored `x` and `y` as raw coordinates and the size of the newly created table has 7,028,074 rows. In the following query you check the metadata of the initial file to see what SRID it uses:\n\n```\nSELECT *\nFROM table(ADVANCED_ANALYTICS.RASTER.PY_EXTRACT_GEOTIFF_METADATA(build_scoped_file_url(@ADVANCED_ANALYTICS.RASTER.FILES, 'Bok_andel.tif')));\n```\n\nThe SRID is `EPSG:25833`. To store data as `GEOGRAPHY` type for further visualisation you need to convert it into `EPSG:4326`. Run the following query:\n\n```\nCREATE OR REPLACE TABLE ADVANCED_ANALYTICS.RASTER.BOK_ANDEL AS\nSELECT TO_GEOGRAPHY(ST_TRANSFORM(ST_MAKEGEOMPOINT(x, y), 25833, 4326)) as geom, band\nFROM ADVANCED_ANALYTICS.RASTER.BOK_ANDEL\n```\n\nAs a final step you visualize the results using H3 cells:\n\n```\nimport streamlit as st\nimport pandas as pd\nimport pydeck as pdk\nfrom typing import List\nimport branca.colormap as cm\nfrom snowflake.snowpark.context import get_active_session\n\nsession = get_active_session()\n\n# Execute the updated SQL query\ndf = session.sql('''select h3_point_to_cell_string(geom, 7) as h3_cell,\n                    st_x(h3_cell_to_point(h3_cell)) as lon,\n                    st_y(h3_cell_to_point(h3_cell)) as lat,\n                    avg(band) as band \n                    FROM ADVANCED_ANALYTICS.RASTER.BOK_ANDEL\n                    group by all;''').to_pandas()\n\ndf[\"BAND\"] = df[\"BAND\"].apply(lambda row: float(row))\ncenter_latitude = df['LAT'].mean()\ncenter_longitude = df['LON'].mean()\n\ndef get_quantiles(df_column: pd.Series, quantiles: List) -\u003E pd.Series:\n    return df_column.quantile(quantiles)\n\ndef get_color(df_column: pd.Series, colors: List, vmin: int, vmax: int, index: pd.Series) -\u003E pd.Series:\n    color_map = cm.LinearColormap(colors, vmin=vmin, vmax=vmax, index=index)\n    return df_column.apply(color_map.rgb_bytes_tuple)\n    \nquantiles = get_quantiles(df[\"BAND\"], [0, 0.2, 0.4, 0.6, 0.8, 1])\ncolors = ['palegreen', 'lightgreen', 'mediumseagreen', 'forestgreen', 'seagreen', 'darkgreen']\n\ndf['BAND'] = get_color(df['BAND'], colors, quantiles.min(), quantiles.max(), quantiles)\n\nst.pydeck_chart(pdk.Deck(\n    map_style=None,\n    initial_view_state=pdk.ViewState(\n        latitude=center_latitude,\n        longitude=center_longitude, \n        zoom=5.2, \n        bearing=0, \n        pitch=0),\n    layers=[\n        pdk.Layer(\n            \"H3HexagonLayer\",\n            df,\n            opacity=0.9,\n            stroked=False,\n            get_hexagon=\"H3_CELL\",\n            get_fill_color='BAND',\n            extruded=False,\n            wireframe=True,\n            line_width_min_pixels=0,\n            auto_highlight=True,\n            pickable=False,\n            filled=True\n        )\n    ],\n))\n```\n\n![assets/geo_ml_27.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_27.png)\n\n### Conclusion\n\nIn this lab, you have learned how to load geospatial data from unstructured formats, such as GeoTiff and Shapefiles and what techniques you can apply when you need to join data using nearest neighbout approach. You can use these or similar UDFs to load data from other formats.\n\n## Creating Interactive Maps with Kepler.gl\n\n\nIn this Lab you will learn how to create interactive maps directly within Snowflake using [Kepler.gl](https://kepler.gl), powered by [Dekart.xyz](https://dekart.xyz/docs/snowflake-snowpark/about/). You will use Dekart.XYZ app and use public datasets from Marketplace to visualize UK highways with color-coded density of nearby EV charging stations. \n\nYor final result will be a map similar to this one:\n\n![assets/geo_ml_39.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_39.png)\n\n### Data aquisition \nFor this project you will use an Overture Maps [Divisions](https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9M9/carto-overture-maps-divisions), [Places](https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9KR/carto-overture-maps-places), and [Transportation](https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9KJ/carto-overture-maps-transportation) datasets offered by CARTO as free Marketplace listins.\n\n- Navigate to the Marketplace screen using the menu on the left side of the window\n- Search for `Overture Maps - Divisions` in the search bar\n- Once in the listing, click the big blue `Get` button\n\n\u003E \n\u003E  On the `Get` screen, you may be prompted to complete your `user profile` if you have not done so before. Click the link as shown in the screenshot below. Enter your name and email address into the profile screen and click the blue `Save` button. You will be returned to the `Get` screen.\n\n![assets/geo_ml_28.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_28.png)\n\nSimilarly you need to find and install [Overture Maps - Places](https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9KR/carto-overture-maps-places), and [Overture Maps - Transportation](https://app.snowflake.com/marketplace/listing/GZT0Z4CM1E9KJ/carto-overture-maps-transportation) datasets.\n\n\u003E \n\u003E  These datasets include information on administrative divisions, transportation routes, and points of interest. The [Overture Maps Schema Reference](https://docs.overturemaps.org/schema/reference/) is an excellent resource to understand the structure and details of each dataset.\n\n### Installing Dekart.xyz\n\nIn this step you will install [Dekart – Kepler.gl maps inside Snowflake](https://app.snowflake.com/marketplace/listing/GZSYZJNO4W/dekart-xyz-dekart-%E2%80%93-kepler-gl-maps-inside-snowflake) application and run it inside of Snowpark Container Services. \n\n\nAs a first step you will install the Marketplace listing:\n- Navigate to the Marketplace screen using the menu on the left side of the window\n- Search for `Dekart – Kepler.gl maps inside Snowflake` in the search bar\n- Once in the listing, click the big blue `Get` button\n- In the \"Warehouse used for installation\" field select the warehouse which will be used for installation process.\n- Click the `Try for Free` button\n\n![assets/geo_ml_29.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_29.png)\n\n\u003E \n\u003E  Note: When trial end you won't be automatically swithched to Subscription-based usage. If you decide to continue using Dekart, you would need to manually enable subscription.\n\nFollow the installation instructions as displayed in the Snowsight interface.\n\n![assets/geo_ml_30.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_30.png)\n\nGrant Account Privileges to Dekart and allow connections to the Mapbox API. Dekart uses Mapbox for rendering maps. No user data is sent to Mapbox. Dekart creates a single node `CPU_X64_XS` compute pool and `XSMALL` warehouse.\n\n![assets/geo_ml_31.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_31.png)\n\nClick `Activate`. Activation process might take up to 10 minutes.\n\n![assets/geo_ml_32.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_32.png)\n\nWhile it's activating you can go to Worksheets and grant access to Overture Maps datasets. This ensures that Dekart can read and visualize the data within Snowflake. Note, tht since you run Dekart withon Snowflake Container Services, your data stays in Snowflake and won't be transfered externally. Execute the following SQL commands in Snowflake (make sure you have the `ACCOUNTADMIN` role for these operations):\n\n```\nGRANT IMPORTED PRIVILEGES ON DATABASE OVERTURE_MAPS__TRANSPORTATION TO application DEKART__KEPLER_GL_MAPS_INSIDE_SNOWFLAKE;\nGRANT IMPORTED PRIVILEGES ON DATABASE OVERTURE_MAPS__DIVISIONS TO application DEKART__KEPLER_GL_MAPS_INSIDE_SNOWFLAKE;\nGRANT IMPORTED PRIVILEGES ON DATABASE OVERTURE_MAPS__PLACES TO application DEKART__KEPLER_GL_MAPS_INSIDE_SNOWFLAKE;\n```\nWhen Activation is done, do the following steps:\n- Open the Dekart App within Snowsight by going to `Data Products` \u003E `Apps`. Selecting Dekart app and click `Launch App`.\n- Authorize the Dekart App with your Snowflake account.\n- In the Dekart interface, click `Create Report` to start building your map.\n\n![assets/geo_ml_33.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_33.png)\n\nCongratulations! You have now Dekart app running in your Snowflake environment and now you're ready to start creating maps!\n\n### Build maps with SQL in Dekart\nDekart allows you to visualize data directly from SQL queries, which means you can write custom queries to shape the data as you like.\n\nIn the new report screen you see three main components: the `SQL` panel on the right, the `Layers` panel on the left and the map in the center. Rename the report, set the name to `Charging Station Density`. Rename the first SQL tab to `uk_boundary` and run the following query:\n\n```\nSELECT ST_ASWKT(GEOMETRY) as GEOMETRY\nFROM OVERTURE_MAPS__DIVISIONS.CARTO.DIVISION_AREA\nWHERE COUNTRY = 'GB' AND SUBTYPE = 'country';\n```\n\nIn this query you use Overture Maps - Divisions dataset to get the shape of the UK boundary. As soon as query is completed, you will see a new layer in the `Layers` panel. You can expand it, to customise if needed, for example to make it transparent you can turn off `Fill color` toggle.\n\n![assets/geo_ml_35.gif](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_35.gif)\n\nAs a next step, add a road network for the UK. Create a new tab in `SQL` panel and name it `uk_roads`. Run the following query that joins road data from `Overture Maps - Transportation` dataset and filters it so it shows only motoways and trunk roads for the UK area:\n\n```\nwith uk_boundary as (SELECT GEOMETRY\nFROM OVERTURE_MAPS__DIVISIONS.CARTO.DIVISION_AREA\nWHERE COUNTRY = 'GB' AND SUBTYPE = 'country')\nSELECT ST_ASWKT(s.GEOMETRY) as GEOMETRY, s.NAMES, s.ID\nFROM OVERTURE_MAPS__TRANSPORTATION.CARTO.SEGMENT s, uk_boundary ub\nWHERE ST_INTERSECTS(ub.GEOMETRY, s.GEOMETRY) AND s.CLASS IN ('motorway', 'trunk');\n```\n\nWhen the query is complete, you'll see the new layer in the Layers panel with the name `uk_roads` and it contains about 126K road segments that are viualised on the map. You can change the colour of the linestrings using `Stroke Color` field in the corresponding Layer.\n\n![assets/geo_ml_36.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_36.png)\n\nIn the next step you will add locations of Electric Vehicles charging sttions as a new layer. Create a new SQL tab, name it `EV_stations` and run the following query:\n\n```\nWITH uk_boundary AS (SELECT GEOMETRY\nFROM OVERTURE_MAPS__DIVISIONS.CARTO.DIVISION_AREA\nWHERE COUNTRY = 'GB' AND SUBTYPE = 'country')\nSELECT ST_ASWKT(p.GEOMETRY) as GEOMETRY\nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE p, uk_boundary ub\nWHERE ST_CONTAINS(ub.GEOMETRY, p.GEOMETRY) AND p.CATEGORIES::TEXT ILIKE '%charging%';\n```\n\nIn the newly created layer `EV_stations` play with `Stroke Color` and `Radius` to adjust the size of points that correspond to chargins stations locations.\n\n![assets/geo_ml_37.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_37.png)\n\nAs a last step, create a new SQL tab, name it `EV_stations_density` and run the following query that for each road segment calculates number of charging stations within 50km radius:\n\n```\nWITH uk_boundary as (SELECT GEOMETRY\nFROM OVERTURE_MAPS__DIVISIONS.CARTO.DIVISION_AREA\nWHERE COUNTRY = 'GB' AND SUBTYPE = 'country'),\nroad_segments as (SELECT s.GEOMETRY, s.NAMES, s.ID\nFROM OVERTURE_MAPS__TRANSPORTATION.CARTO.SEGMENT s, uk_boundary ub\nWHERE ST_INTERSECTS(ub.GEOMETRY, s.GEOMETRY) AND s.CLASS IN ('motorway', 'trunk')),\ncharging_stations as (SELECT p.GEOMETRY\nFROM OVERTURE_MAPS__PLACES.CARTO.PLACE p, uk_boundary ub\nWHERE ST_CONTAINS(ub.GEOMETRY, p.GEOMETRY) AND p.CATEGORIES::TEXT ILIKE '%charging%'),\ncharging_count AS (\n   SELECT r.ID AS road_id, r.NAMES AS road_name, COUNT(cs.GEOMETRY) AS num_charging_stations\n   FROM road_segments r\n   LEFT JOIN charging_stations cs ON ST_DISTANCE(r.GEOMETRY, cs.GEOMETRY) \u003C= 50000\n   GROUP BY r.ID, r.NAMES\n)\nSELECT r.ID, r.NAMES, ST_ASWKT(r.GEOMETRY) as GEOMETRY, cc.num_charging_stations\nFROM road_segments r\nJOIN charging_count cc ON r.ID = cc.road_id;\n```\n\nThis is our final visualization. Before editing its look and feel, you can hide other layers by clicking on the 'eye' icon. Then, select `EV_stations_density`, click on the three dots next to the `Stroke Color` field, and choose `NUM_CHARGING_STATIONS` as the source for the stroke color. You can also change the color map and select a color scheme of your choice.\n\n![assets/geo_ml_38.gif](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_38.gif)\n\nYou can now use top right menu to save the newly created map and to share it within your organization.\n\n### Conclusion\n\nIn this Lab, you created an interactive, real-time map within Snowflake, using Dekart and the Overture Maps datasets. You explored UK highway infrastructure with a focus on EV charging station density.\n\n![assets/geo_ml_39.png](https://www.snowflake.com/content/dam/snowflake-site/developers/guides/geo-for-machine-learning/geo_ml_39.png)\n\n#### What You Learned\n- Creating interactive maps directly within Snowflake using Kepler.gl and Dekart.\n- Accessing and using public Overture Maps data to create meaningful geospatial visualizations.\n- Writing SQL queries for filtering, calculating, and mapping geospatial data.\n\n#### Resources\n- [Snowflake Kepler.gl Maps Examples](https://dekart.xyz/docs/about/snowflake-kepler-gl-examples/): Explore more examples and use cases for Kepler.gl in Snowflake.\n- [Dekart Snowpark Application Documentation](https://dekart.xyz/docs/snowflake-snowpark/about/): Learn more about Dekart and its capabilities.\n- [Overture Maps Schema Reference](https://docs.overturemaps.org/schema/reference/): For more details on available tables and fields.\n\n## Conclusion And Resources\n\n\nCongratulations! You've successfully performed data analytics, data engineering and data science tasks for various use cases.\nCongratulations! You've successfully performed data analytics, data engineering and data science tasks for various use cases.\n\n### What You Learned\n\n* How to acquire data from the Snowflake Marketplace\n* How to load data from external storage\n* How to transform geospatial data using H3 and Time Series functions\n* How to train models and predict results with Cortex ML\n* How to use LLM for analysing textual data\n* How to visualize data with Streamlit\n\n### Related Resources\n- [Geospatial Analytics for Retail with Snowflake and CARTO](/en/developers/guides/geospatial-analytics-with-snowflake-and-carto-ny/)\n- [Geospatial Analysis using Geometry and Geography Data Types quickstart](/en/developers/guides/geo-analysis-geometry/) \n- [Performance Optimization Techniques for Geospatial queries](/en/developers/guides/geo-performance/)\n\nWe would love your feedback on this QuickStart Guide! Please submit your feedback using this [Feedback Form](https://forms.gle/tGDzTpu41huWFDXi9).\n\n\nWe would love your feedback on this QuickStart Guide! Please submit your feedback using this [Feedback Form](https://forms.gle/tGDzTpu41huWFDXi9).\n\n","multiValue":false,":type":"text/x-markdown"},"quickstartArticleLogoImage":{"dataType":"string","title":"Quickstart Article Logo Image","multiValue":false,":type":"text/plain"}},"elementsOrder":["quickstartArticleBody","quickstartArticleLogoImage"],"model":"snowflake-site/models/quickstart-article"},"flexible_column_cont":{"id":"flexible-column-container-8aaaffefc9","type":"2-column-75-25","alignColumns":"top","containerMaxWidth":"extra-large","topPadding":"none","bottomPadding":"none","spaceBetween":"none","reverseOnMobile":false,"carouselOnMobile":false,"backgroundImageOption":"none","flexible_column_content_container_1":{"layout":"SIMPLE","id":"container-3a025466d8",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"quickstart_last_modi":{"id":"quickstart-last-modified-726bf56001","icon":{"id":"icon","icon":"calendar",":type":"snowflake-site/components/icon","appliedCssClassNames":"snowflake-icon-blue"},"lastModifiedDatePrefix":"Updated","lastModifiedDate":"2025-12-20",":type":"snowflake-site/components/quickstart/quickstart-last-modified","appliedCssClassNames":"snowflake-responsive-component-top-padding-small"},"text":{"id":"text-e212f0bead","additionalClasses":"qs-disclaimer-text","text":"\u003Cp\u003E\u003Cspan style=\"color: #666;\"\u003EThis content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances\u003C/span\u003E\u003C/p\u003E\r\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"snowflake-responsive-component-top-padding-small"}},":itemsOrder":["quickstart_last_modi","text"]},"flexible_column_content_container_2":{"layout":"SIMPLE","id":"container-5fe8f0f899",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{},":itemsOrder":[]},"isBlogPage":false,"isActiveTOC":false,":type":"snowflake-site/components/flexible-column-container"}},":itemsOrder":["contentfragment","flexible_column_cont"]},"flexible_column_content_container_2":{"layout":"SIMPLE","id":"container-9cbaeb3344",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"quickstart_table_of_":{"layout":"SIMPLE","id":"container-9cc16ed60f","isDeveloperGuidesPage":false,":type":"snowflake-site/components/quickstart/quickstart-table-of-content/quickstart-table-of-content-container",":items":{"quickstart_table_of_":{"id":"quickstart-table-of-content-f8e1e9dee9","fragmentPath":"/content/dam/snowflake-site/en/content-fragments/quickstarts/geo-for-machine-learning",":type":"snowflake-site/components/quickstart/quickstart-table-of-content","headings":["\u003Ch2\u003EOverview\u003C/h2\u003E","\u003Ch2\u003ESetup your Account\u003C/h2\u003E","\u003Ch2\u003EGeospatial 101\u003C/h2\u003E","\u003Ch2\u003EEnergy grids analysis using GEOMETRY\u003C/h2\u003E","\u003Ch2\u003EGeocoding and Reverse Geocoding\u003C/h2\u003E","\u003Ch2\u003EForecasting time series on a map\u003C/h2\u003E","\u003Ch2\u003ECustomer Reviews Sentiment Analysis\u003C/h2\u003E","\u003Ch2\u003EProcessing unstructured geospatial data\u003C/h2\u003E","\u003Ch2\u003ECreating Interactive Maps with Kepler.gl\u003C/h2\u003E","\u003Ch2\u003EConclusion And Resources\u003C/h2\u003E"]},"quickstart_button":{"id":"quickstart-button-a0c5218c29","fragmentPath":"/content/dam/snowflake-site/en/content-fragments/quickstarts/geo-for-machine-learning",":type":"snowflake-site/components/quickstart/quickstart-button","appliedCssClassNames":"snowflake-responsive-component-top-padding-none"}},":itemsOrder":["quickstart_table_of_","quickstart_button"]}},":itemsOrder":["quickstart_table_of_"]},"isBlogPage":false,"isActiveTOC":false,":type":"snowflake-site/components/flexible-column-container"},"markup_editor":{"id":"markup-editor-b0db3def82","title":"Page CSS","cssContent":"#quickstart-template-main-flexible-container{padding:24px}#quickstart-template-main-flexible-container \u003E .snowflake-flexible-column-container-items{grid-template-columns:1fr 0}.qs-disclaimer-text p \u003E span{font-size:15px !important}@media (min-width:768px){#quickstart-template-main-flexible-container{padding:24px 32px}#quickstart-template-main-flexible-container \u003E .snowflake-flexible-column-container-items{grid-template-columns:7fr 3fr;gap:48px}}@media (max-width:767px){#quickstart-template-main-flexible-container \u003E .snowflake-flexible-column-container-items{gap:0}}@media (min-width:1024px){#quickstart-template-main-flexible-container{padding:0 92px 48px 92px}#quickstart-template-main-flexible-container \u003E .snowflake-flexible-column-container-items{gap:117px}}","isGSAPEnabled":false,":type":"snowflake-site/components/markup-editor"}},":itemsOrder":["quickstart_hero","flexible_column_cont","markup_editor"],":type":"wcm/foundation/components/responsivegrid"},"modal_container":{"layout":"SIMPLE","id":"container-87a4bda00c",":type":"snowflake-site/components/modal/modal-container",":items":{},":itemsOrder":[]},"experiencefragment-footer":{"id":"experiencefragment-9b4522bcc5","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/footer/master/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment",":items":{"root":{"additionalClasses":"sf-footer","layout":"SIMPLE","id":"container-1d97014c0b",":type":"snowflake-site/components/container",":items":{"container_copy":{"additionalClasses":"sf-footer__inner","columnClassNames":{"flexible_column_cont":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-daf2f8f473",":type":"snowflake-site/components/container",":items":{"flexible_column_cont":{"id":"flexible-column-container-ae5dd72f4c","type":"1-column","alignColumns":"top","containerMaxWidth":"extra-large","topPadding":"medium","bottomPadding":"extra-small","spaceBetween":"small","reverseOnMobile":false,"carouselOnMobile":false,"propertiesCSSClasses":"sf-footer-grid","backgroundImageOption":"none","flexible_column_content_container_1":{"layout":"SIMPLE","id":"container-9e2faeb177",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"container":{"additionalClasses":"sf-footer-grid__inner","columnClassNames":{"container":"aem-GridColumn aem-GridColumn--default--12","container_1622723482":"aem-GridColumn aem-GridColumn--default--12","container_copy_copy_":"aem-GridColumn aem-GridColumn--default--12","container_copy_copy":"aem-GridColumn aem-GridColumn--default--12","container_copy":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-345e2e0d61",":type":"snowflake-site/components/container",":items":{"container_1622723482":{"additionalClasses":"sf-footer__column","columnClassNames":{"container":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-3ef1a028dd",":type":"snowflake-site/components/container",":items":{"container":{"additionalClasses":"sf-footer__newsletter-group","columnClassNames":{"text":"aem-GridColumn aem-GridColumn--default--12","marketo_v2":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-60bca9041b",":type":"snowflake-site/components/container",":items":{"text":{"id":"text-7cceb4f388","additionalClasses":"sf-footer__newsletter-title","text":"\u003Cp\u003E\u003Cb\u003ESubscribe to our monthly newsletter\u003C/b\u003E\u003C/p\u003E\r\n\u003Cp\u003EStay up to date on Snowflake’s latest products, expert insights and resources—right in your inbox!\u003C/p\u003E\r\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-regular text-color-text-04"},"marketo_v2":{"id":"marketo-v2-6659371225","marketoForm":{"formId":"45871","edit":false,"successUrl":null,"hidden":null,"script":null,"values":null},"marketoConfigured":true,"formConfigured":true,"munchkinId":"252-RFO-227","serverInstance":"252-RFO-227.mktoweb.com",":type":"snowflake-site/components/form/marketo-v2"}},":itemsOrder":["text","marketo_v2"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"}},":itemsOrder":["container"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"},"container":{"columnClassNames":{"text_copy":"aem-GridColumn aem-GridColumn--default--12","text":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-051633fb7a",":type":"snowflake-site/components/container",":items":{"text":{"id":"text-96b6f60240","additionalClasses":"sf-footer__link-group","text":"\u003Cp class=\"sf-footer__column-title\"\u003EProduct\u003C/p\u003E\r\n\u003Cul\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/product/platform/\"\u003EPlatform\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/product/snowflake-cowork/\"\u003ESnowflake CoWork\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/product/data-engineering/\"\u003EData Engineering\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/product/analytics/\"\u003EAnalytics\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/product/ai/\"\u003EAI\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/product/applications-and-collaboration/\"\u003EApplications &amp; Collaboration\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/pricing-options/\"\u003EPricing\u003C/a\u003E\u003C/li\u003E\r\n\u003C/ul\u003E\r\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"},"text_copy":{"id":"text-3c6b10c7b8","additionalClasses":"sf-footer__link-group","text":"\u003Cp class=\"sf-footer__column-title\"\u003ESupport\u003C/p\u003E\r\n\u003Cul\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/support/\"\u003ESupport\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/legal/addenda/priority-support-services-description/\"\u003EPriority Support\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://status.snowflake.com/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003EStatus\u003C/a\u003E\u003C/li\u003E\r\n\u003C/ul\u003E\r\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"}},":itemsOrder":["text","text_copy"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-medium"},"container_copy_copy":{"columnClassNames":{"text":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-2b0cd2d2a5",":type":"snowflake-site/components/container",":items":{"text":{"id":"text-1b8861f97c","additionalClasses":"sf-footer__link-group","text":"\u003Cp class=\"sf-footer__column-title\"\u003E\u003Ca href=\"/en/solutions/industries/\"\u003EIndustries\u003C/a\u003E\u003C/p\u003E\r\n\u003Cul\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/solutions/industries/advertising-media-entertainment/\"\u003EAdvertising, Media &amp; Entertainment\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/solutions/industries/financial-services/\"\u003EFinancial Services\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/solutions/industries/healthcare-and-life-sciences/\"\u003EHealthcare &amp; Life Sciences\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/solutions/industries/manufacturing/\"\u003EManufacturing\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/solutions/industries/public-sector/\"\u003EPublic Sector\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/solutions/industries/retail-consumer-goods/\"\u003ERetail &amp; Consumer Goods\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/solutions/industries/telecom/\"\u003ETelecom\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/solutions/industries/technology/\"\u003ETechnology\u003C/a\u003E\u003C/li\u003E\r\n\u003C/ul\u003E\r\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"}},":itemsOrder":["text"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"},"container_copy":{"columnClassNames":{"text":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-adbcf6e901",":type":"snowflake-site/components/container",":items":{"text":{"id":"text-cdae86b1c4","additionalClasses":"sf-footer__link-group","text":"\u003Cp class=\"sf-footer__column-title\"\u003ECompany\u003C/p\u003E\r\n\u003Cul\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/company/overview/about-snowflake/\"\u003EAbout Snowflake\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/company/overview/leadership-and-board/\"\u003ELeadership &amp; Board\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://careers.snowflake.com/us/en\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ECareers\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://investors.snowflake.com/overview/default.aspx\" target=\"_blank\" rel=\"noopener noreferrer\"\u003EInvestor Relations\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://trust.snowflake.com/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003ETrust Center\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/brand-guidelines/\" target=\"_blank\" rel=\"noopener noreferrer\"\u003EBrand Guidelines\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/contact/\"\u003EContact\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/news/\"\u003ENewsroom\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/company/overview/esg/\"\u003EEnvironmental, Social &amp; Governance\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/company/overview/snowflake-ventures/\"\u003ESnowflake Ventures\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/company/overview/end-data-disparity/\"\u003EEnd Data Disparity\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/summit/\"\u003ESnowflake Summit 26\u003C/a\u003E\u003C/li\u003E\r\n\u003C/ul\u003E\r\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"}},":itemsOrder":["text"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"},"container_copy_copy_":{"columnClassNames":{"text":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-70086a1fd7",":type":"snowflake-site/components/container",":items":{"text":{"id":"text-9833fc6ecb","additionalClasses":"sf-footer__link-group","text":"\u003Cp class=\"sf-footer__column-title\"\u003ELearn\u003C/p\u003E\r\n\u003Cul\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://snowflake.com/en/resources/\"\u003EResource Library\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/webinars/demo/\"\u003ELive Demos\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/fundamentals/\"\u003EFundamentals\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/resources/learn/training/\"\u003ETraining\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/resources/learn/certifications/\"\u003ECertifications\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca rel=\"noopener noreferrer\" target=\"_blank\" href=\"https://learn.snowflake.com/en/\"\u003ESnowflake University\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/developers/guides\"\u003EDeveloper Guides\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca rel=\"noopener noreferrer\" target=\"_blank\" href=\"https://docs.snowflake.com/\"\u003EDocumentation\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"/en/data-governance/\"\u003EData Governance\u003C/a\u003E\u003C/li\u003E\r\n\u003C/ul\u003E\r\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"}},":itemsOrder":["text"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"}},":itemsOrder":["container_1622723482","container","container_copy_copy","container_copy","container_copy_copy_"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"}},":itemsOrder":["container"]},"isBlogPage":false,"isActiveTOC":false,":type":"snowflake-site/components/flexible-column-container"}},":itemsOrder":["flexible_column_cont"],"appliedCssClassNames":"snowflake-container snowflake-responsive-container-inner-padding-small"},"container_573483281_":{"additionalClasses":"sf-footer__bottom","columnClassNames":{"container_112062425":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-36a11bad2e",":type":"snowflake-site/components/container",":items":{"container_112062425":{"columnClassNames":{"flexible_column_cont":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-b3cc5000da",":type":"snowflake-site/components/container",":items":{"flexible_column_cont":{"id":"flexible-column-container-e1c639cdf3","type":"1-column","alignColumns":"top","containerMaxWidth":"extra-large","topPadding":"none","bottomPadding":"none","spaceBetween":"small","reverseOnMobile":false,"carouselOnMobile":false,"backgroundImageOption":"none","flexible_column_content_container_1":{"layout":"SIMPLE","id":"container-8fc884dbea",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"container":{"additionalClasses":"sf-footer__legal-container","columnClassNames":{"container":"aem-GridColumn aem-GridColumn--default--12","text_copy_copy_16360":"aem-GridColumn aem-GridColumn--default--12","markup_editor":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-bd1631b932",":type":"snowflake-site/components/container",":items":{"container":{"columnClassNames":{"image":"aem-GridColumn aem-GridColumn--default--12"},"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","layout":"RESPONSIVE_GRID","columnCount":12,"id":"container-77584275a9",":type":"snowflake-site/components/container",":items":{"image":{"id":"image-ac8c672a31","additionalClasses":"sf-footer__logo","lazyEnabled":true,"src":"https://www.snowflake.com/content/experience-fragments/snowflake-site/language-masters/en/site/footer/master/_jcr_content/root/container_573483281_/container_112062425/flexible_column_cont/flexible_column_content_container_1/container/container/image.coreimg.svg/1747882370694/nav-icon-snowflake-bug.svg","alt":"Snowflake logo","imageLink":{"valid":true,"url":"/en/"},"height":"64","width":"64",":type":"snowflake-site/components/image"}},":itemsOrder":["image"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-extra-small"},"text_copy_copy_16360":{"id":"text-de1956708a","additionalClasses":"sf-footer__legal-links","text":"\u003Cul\u003E\r\n\u003Cli\u003E© 2026 Snowflake Inc. All Rights Reserved\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/legal/privacy/privacy-policy/\"\u003EPrivacy Policy\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://snowflake.com/en/legal/snowflake-site-terms/\"\u003ESite Terms\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://info.snowflake.com/Preference-center.html\"\u003ECommunication Preferences\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Cbutton id=\"ot-sdk-btn\" class=\"ot-sdk-show-settings\"\u003ECookie Settings\u003C/button\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/legal/privacy/privacy-policy/#12\"\u003EDo Not Share My Personal Information\u003C/a\u003E\u003C/li\u003E\r\n\u003Cli\u003E\u003Ca href=\"https://www.snowflake.com/en/legal/\"\u003ELegal\u003C/a\u003E\u003C/li\u003E\r\n\u003C/ul\u003E\r\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"},"markup_editor":{"id":"markup-editor-9495ff0b90","title":" ","htmlContent":"\u003Cdiv class=\"sf-footer__social\"\u003E\r\n\u003Cdiv data-testid=\"snowflake-footer-twitter\" class=\"snowflake-button-icon snowflake-button-white snowflake-footer-social-item\"\u003E\u003Cdiv class=\"snowflake-button-icon \"\u003E\u003Ca href=\"https://x.com/Snowflake\" data-testid=\"button-external\" aria-label=\"X (Twitter)\" role=\"button\" class=\"snowflake-button-container\" title=\"X (Twitter)\" tabindex=\"0\" target=\"_blank\" rel=\"noreferrer\"\u003E\u003Cdiv data-testid=\"button-icon-wrapper\"\u003E\u003Csvg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 59 53\" class=\"button-icon\"\u003E\u003Cpath fill=\"currentColor\" d=\"M46.614 0h9.044L35.8 22.49 59 53H40.795L26.54 34.46 10.223 53H1.18l21.036-24.055L0 0h18.657l12.878 16.937zM43.45 47.72h5.013L16.023 5.085h-5.387z\"\u003E\u003C/path\u003E\u003C/svg\u003E\u003C/div\u003E\u003C/a\u003E\u003Cdiv\u003E\u003C/div\u003E\u003C/div\u003E\u003C/div\u003E\u003Cdiv data-testid=\"snowflake-footer-linkedin\" class=\"snowflake-button-icon snowflake-button-white snowflake-footer-social-item\"\u003E\u003Cdiv class=\"snowflake-button-icon \"\u003E\u003Ca href=\"https://www.linkedin.com/company/3653845\" data-testid=\"button-external\" aria-label=\"LinkedIn\" role=\"button\" class=\"snowflake-button-container\" title=\"LinkedIn\" tabindex=\"0\" target=\"_blank\" rel=\"noreferrer\"\u003E\u003Cdiv data-testid=\"button-icon-wrapper\"\u003E\u003Csvg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\" class=\"button-icon\"\u003E\u003Cpath d=\"M22.223 0H1.772C.792 0 0 .773 0 1.73v20.536C0 23.222.792 24 1.772 24h20.451c.98 0 1.777-.778 1.777-1.73V1.73C24 .773 23.203 0 22.223 0ZM7.12 20.452H3.558V8.995H7.12v11.457ZM5.34 7.434a2.064 2.064 0 1 1 0-4.125 2.063 2.063 0 0 1 0 4.125Zm15.112 13.018h-3.558v-5.57c0-1.326-.024-3.037-1.852-3.037-1.851 0-2.133 1.449-2.133 2.944v5.663H9.356V8.995h3.413v1.566h.047c.473-.9 1.636-1.852 3.365-1.852 3.605 0 4.27 2.372 4.27 5.457v6.286Z\"\u003E\u003C/path\u003E\u003C/svg\u003E\u003C/div\u003E\u003C/a\u003E\u003Cdiv\u003E\u003C/div\u003E\u003C/div\u003E\u003C/div\u003E\u003Cdiv data-testid=\"snowflake-footer-facebook\" class=\"snowflake-button-icon snowflake-button-white snowflake-footer-social-item\"\u003E\u003Cdiv class=\"snowflake-button-icon \"\u003E\u003Ca href=\"https://www.facebook.com/snowflakedb/\" data-testid=\"button-external\" aria-label=\"Facebook\" role=\"button\" class=\"snowflake-button-container\" title=\"Facebook\" tabindex=\"0\" target=\"_blank\" rel=\"noreferrer\"\u003E\u003Cdiv data-testid=\"button-icon-wrapper\"\u003E\u003Csvg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\" class=\"button-icon\"\u003E\u003Cpath d=\"M24 12c0-6.627-5.373-12-12-12S0 5.373 0 12c0 5.99 4.388 10.954 10.125 11.854V15.47H7.078V12h3.047V9.356c0-3.007 1.792-4.668 4.533-4.668 1.312 0 2.686.234 2.686.234v2.953H15.83c-1.491 0-1.956.925-1.956 1.875V12h3.328l-.532 3.469h-2.796v8.385C19.612 22.954 24 17.99 24 12Z\"\u003E\u003C/path\u003E\u003C/svg\u003E\u003C/div\u003E\u003C/a\u003E\u003Cdiv\u003E\u003C/div\u003E\u003C/div\u003E\u003C/div\u003E\u003Cdiv data-testid=\"snowflake-footer-youtube\" class=\"snowflake-button-icon snowflake-button-white snowflake-footer-social-item\"\u003E\u003Cdiv class=\"snowflake-button-icon \"\u003E\u003Ca href=\"https://www.youtube.com/user/snowflakecomputing\" data-testid=\"button-external\" aria-label=\"YouTube\" role=\"button\" class=\"snowflake-button-container\" title=\"YouTube\" tabindex=\"0\" target=\"_blank\" rel=\"noreferrer\"\u003E\u003Cdiv data-testid=\"button-icon-wrapper\"\u003E\u003Csvg xmlns=\"http://www.w3.org/2000/svg\" fill=\"currentColor\" viewBox=\"0 0 24 24\" class=\"button-icon\"\u003E\u003Cpath d=\"M23.76 7.2s-.233-1.655-.955-2.381c-.914-.956-1.936-.961-2.405-1.017-3.356-.244-8.395-.244-8.395-.244h-.01s-5.039 0-8.395.244c-.469.056-1.49.06-2.405 1.017C.473 5.545.244 7.2.244 7.2S0 9.145 0 11.086v1.819c0 1.94.24 3.886.24 3.886s.233 1.654.95 2.38c.915.957 2.115.924 2.65 1.027 1.92.183 8.16.24 8.16.24s5.044-.01 8.4-.249c.469-.056 1.49-.06 2.405-1.017.722-.727.956-2.381.956-2.381S24 14.85 24 12.905v-1.819c0-1.94-.24-3.886-.24-3.886ZM9.52 15.113V8.367l6.483 3.385-6.483 3.36Z\"\u003E\u003C/path\u003E\u003C/svg\u003E\u003C/div\u003E\u003C/a\u003E\u003Cdiv\u003E\u003C/div\u003E\u003C/div\u003E\u003C/div\u003E\r\n\u003C/div\u003E","isGSAPEnabled":false,":type":"snowflake-site/components/markup-editor"}},":itemsOrder":["container","text_copy_copy_16360","markup_editor"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-none"}},":itemsOrder":["container"]},"isBlogPage":false,"isActiveTOC":false,":type":"snowflake-site/components/flexible-column-container"}},":itemsOrder":["flexible_column_cont"],"appliedCssClassNames":"snowflake-container snowflake-responsive-container-inner-padding-small"}},":itemsOrder":["container_112062425"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-none"},"markup_editor_copy":{"id":"markup-editor-48307168aa","title":"New css","cssContent":".snowflake-image-container img{background-color:transparent}div.snowflake-person-chip-avatar{width:80px !important}#snowflake-blog-template-main-container .snowflake-quote-item-card{margin-top:40px}#snowflake-blog-template-main-container .aem-GridColumn:has(.vertical-video){background-color:#000;border-radius:16px;overflow:hidden}#snowflake-blog-template-main-container .is-vertical img{max-width:400px;margin-left:auto;margin-right:auto}#snowflake-blog-template-main-container .vertical-video{max-width:240px;margin-left:auto;margin-right:auto}@media screen and (min-width:1367px){.dynamic .heading-1-v2 .snowflake-title-v2-line{font-size:72px !important;line-height:60px !important}}.snowflake-flexible-column-container-items-alignment-match-height .download-card,.snowflake-flexible-column-container-items-alignment-match-height .download-card\u003E.container{height:100%}.download-card div.code-toolbar\u003E.toolbar .copy-to-clipboard-button{background-color:white;border:1px solid #a9e1f6;margin-right:4px;top:6px;border-radius:16px;height:26px;width:40px}.download-card .snowflake-code-snippet\u003Ediv.code-toolbar\u003E.toolbar\u003E.toolbar-item\u003Ebutton:before{content:'';background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='9' width='13' height='13' rx='2' ry='2' style='stroke:%23249EDC;'%3E%3C/rect%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1' style='stroke:%23249EDC;'%3E%3C/path%3E%3C/svg%3E\");background-size:auto 65%;background-position:center;background-repeat:no-repeat;top:0;left:0;width:100%;height:100%}.download-card .snowflake-code-snippet\u003Ediv.code-toolbar\u003E.toolbar\u003E.toolbar-item\u003Ebutton:hover:before{background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='9' width='13' height='13' rx='2' ry='2' style='stroke:%23fff;'%3E%3C/rect%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1' style='stroke:%23fff;'%3E%3C/path%3E%3C/svg%3E\")}.download-card\u003Ediv{background-color:#fff;border:1px solid #ccc;border-radius:8px;padding:24px}.download-chip__headline{border-bottom:1px solid #ccc;padding-bottom:16px;margin-bottom:16px}.download-chip{padding:8px 12px !important;border-radius:4px;transition:300ms ease background-color}.download-chip .black-blue-text-color .snowflake-title-v2-line{color:#000 !important;padding-right:24px;font-family:'Lato',sans-serif;font-size:14px !important;font-weight:500 !important}.download-chip .black-blue-text-color .snowflake-title-v2-line:not(:first-child){opacity:.6;font-style:italic !important}.download-chip .snowflake-content-chip-button{display:none}.download-chip.is-external-link{background-size:16px 16px;background-image:url(\"data:image/svg+xml,%3Csvg width='15' height='15' viewBox='0 0 15 15' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.06055 13.0607L11.8605 2.26067M13.0605 10.6607V1.06067H3.46055' stroke='%23249EDC' stroke-width='2.12132' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A\")}.download-chip{background-image:url(\"data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_883_7979)'%3E%3Cpath d='M3.375 16.875H14.625' stroke='%23249EDC' stroke-width='1.40625' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M9 1.125V11.25' stroke='%23249EDC' stroke-width='1.40625' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M4.5 7.875L9 12.375L13.5 7.875' stroke='%23249EDC' stroke-width='1.40625' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/g%3E%3Cdefs%3E%3CclipPath id='clip0_883_7979'%3E%3Crect width='18' height='18' fill='white'/%3E%3C/clipPath%3E%3C/defs%3E%3C/svg%3E%0A\");background-size:24px auto;background-repeat:no-repeat;background-position:calc(100% - 12px) center}.download-chip__headline{display:flex;gap:16px;flex-direction:row !important;flex-wrap:nowrap}.download-chip__headline::before{content:'';display:inline-block;width:24px;height:24px;background-position:center;background-image:url(\"data:image/svg+xml,%3Csvg width='21' height='21' viewBox='0 0 21 21' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M7.50005 9.89999C8.13657 9.89999 8.74702 9.64713 9.19711 9.19704C9.64719 8.74696 9.90005 8.13651 9.90005 7.49999V2.69999C9.90005 2.06347 9.64719 1.45302 9.19711 1.00293C8.74702 .552844 8.13657 .299988 7.50005 .299988H2.70005C2.06353 .299988 1.45308 .552844 1.00299 1.00293C.552905 1.45302 .300049 2.06347 .300049 2.69999V7.49999C.300049 8.13651 .552905 8.74696 1.00299 9.19704C1.45308 9.64713 2.06353 9.89999 2.70005 9.89999H7.50005ZM7.50005 7.49999H2.70005V2.69999H7.50005V7.49999Z' fill='%23249EDC' stroke='white' stroke-width='.6'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M7.50005 20.3C8.13657 20.3 8.74702 20.0472 9.19711 19.5971C9.64719 19.147 9.90005 18.5365 9.90005 17.9V13.1C9.90005 12.4635 9.64719 11.853 9.19711 11.403C8.74702 10.9529 8.13657 10.7 7.50005 10.7H2.70005C2.06353 10.7 1.45308 10.9529 1.00299 11.403C.552905 11.853 .300049 12.4635 .300049 13.1V17.9C.300049 18.5365 .552905 19.147 1.00299 19.5971C1.45308 20.0472 2.06353 20.3 2.70005 20.3H7.50005ZM7.50005 17.9H2.70005V13.1H7.50005V17.9Z' fill='%23249EDC' stroke='white' stroke-width='.6'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M17.9001 9.89999C18.5366 9.89999 19.147 9.64713 19.5971 9.19704C20.0472 8.74696 20.3001 8.13651 20.3001 7.49999V2.69999C20.3001 2.06347 20.0472 1.45302 19.5971 1.00293C19.147 .552844 18.5366 .299988 17.9001 .299988H13.1001C12.4636 .299988 11.8531 .552844 11.403 1.00293C10.9529 1.45302 10.7001 2.06347 10.7001 2.69999V7.49999C10.7001 8.13651 10.9529 8.74696 11.403 9.19704C11.8531 9.64713 12.4636 9.89999 13.1001 9.89999H17.9001ZM17.9001 7.49999H13.1001V2.69999H17.9001V7.49999Z' fill='%23249EDC' stroke='white' stroke-width='.6'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M17.9001 20.3C18.5366 20.3 19.147 20.0472 19.5971 19.5971C20.0472 19.147 20.3001 18.5365 20.3001 17.9V13.1C20.3001 12.4635 20.0472 11.853 19.5971 11.403C19.147 10.9529 18.5366 10.7 17.9001 10.7H13.1001C12.4636 10.7 11.8531 10.9529 11.403 11.403C10.9529 11.853 10.7001 12.4635 10.7001 13.1V17.9C10.7001 18.5365 10.9529 19.147 11.403 19.5971C11.8531 20.0472 12.4636 20.3 13.1001 20.3H17.9001ZM17.9001 17.9H13.1001V13.1H17.9001V17.9Z' fill='%23249EDC' stroke='white' stroke-width='.6'/%3E%3C/svg%3E%0A\");background-size:contain;background-repeat:no-repeat}.download-chip__headline.is-cli::before{background-image:url(\"data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 17L10 11L4 5' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M12 19H20' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A\")}.download-card pre[class*=language-]{padding:8px 12px;background-color:var(--ui-background-05);overflow:hidden}.download-chip__headline.is-windows,.download-chip__headline.is-mac{gap:12px}.download-chip__headline.is-windows::before{width:16px;height:20px;background-image:url(\"data:image/svg+xml,%3Csvg width='4875' height='4875' viewBox='0 0 4875 4875' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg clip-path='url(%23clip0_122_201)'%3E%3Cpath d='M0 0H2311V2310H0V0ZM2564 0H4875V2310H2564V0ZM0 2564H2311V4875H0V2564ZM2564 2564H4875V4875H2564' fill='%23000'/%3E%3C/g%3E%3C/svg%3E\")}.download-chip__headline.is-mac::before{width:16px;height:20px;background-image:url(\"data:image/svg+xml,%3Csvg version='1.1' id='Layer_1' xmlns:x='ns_extend;' xmlns:i='ns_ai;' xmlns:graph='ns_graphs;' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0' y='0' viewBox='0 0 41.5 51' style='enable-background:new 0 0 41.5 51;' xml:space='preserve'%3E%3Cmetadata%3E%3Csfw xmlns='ns_sfw;'%3E%3Cslices%3E%3C/slices%3E%3CsliceSourceBounds bottomLeftOrigin='true' height='51' width='41.5' x='166.1' y='-208.1'%3E%3C/sliceSourceBounds%3E%3C/sfw%3E%3C/metadata%3E%3Cg%3E%3Cpath d='M40.2,17.4c-3.4,2.1-5.5,5.7-5.5,9.7c0,4.5,2.7,8.6,6.8,10.3c-.8,2.6-2,5-3.5,7.2c-2.2,3.1-4.5,6.3-7.9,6.3s-4.4-2-8.4-2 c-3.9,0-5.3,2.1-8.5,2.1s-5.4-2.9-7.9-6.5C2,39.5,.1,33.7,0,27.6c0-9.9,6.4-15.2,12.8-15.2c3.4,0,6.2,2.2,8.3,2.2 c2,0,5.2-2.3,9-2.3C34.1,12.2,37.9,14.1,40.2,17.4z M28.3,8.1C30,6.1,30.9,3.6,31,1c0-.3,0-.7-.1-1c-2.9,.3-5.6,1.7-7.5,3.9 c-1.7,1.9-2.7,4.3-2.8,6.9c0,.3,0,.6,.1,.9c.2,0,.5,.1,.7,.1C24.1,11.6,26.6,10.2,28.3,8.1z'%3E%3C/path%3E%3C/g%3E%3C/svg%3E\")}.download-chip__headline.is-desktop::before{background-image:url(\"data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cg opacity='.8'%3E%3Cpath d='M1.5 21H22.5V18H1.5V21Z' fill='%23000' stroke='white' stroke-width='.75'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M19.5 15C20.2956 15 21.0587 14.6839 21.6213 14.1213C22.1839 13.5587 22.5 12.7956 22.5 12V6C22.5 5.20435 22.1839 4.44129 21.6213 3.87868C21.0587 3.31607 20.2956 3 19.5 3H4.5C3.70435 3 2.94129 3.31607 2.37868 3.87868C1.81607 4.44129 1.5 5.20435 1.5 6V12C1.5 12.7956 1.81607 13.5587 2.37868 14.1213C2.94129 14.6839 3.70435 15 4.5 15H19.5ZM19.5 12H4.5V6H19.5V12Z' fill='%23000' stroke='white' stroke-width='.75'/%3E%3C/g%3E%3C/svg%3E%0A\")}.download-card .snowflake-code-snippet,.download-card .snowflake-code-snippet code,.download-card .snowflake-code-snippet pre{font-size:14px;color:#000;text-shadow:none !important}.download-chip:hover{background-color:var(--ui-background-05) !important;transition:300ms ease background-color}body:has(.snowflake-skip-to-content[style]) #subNav,.pushdown-banner-dismissed #subNav{top:var(--scroll-padding-top) !important;transition:300ms ease top}body:has(.snowflake-skip-to-content[style*=\"58\"]) #subNav{top:34px !important}body:has(.snowflake-skip-to-content[style*=\"82\"]) #subNav{top:58px !important}body:has(.snowflake-skip-to-content[style*=\"130\"]) #subNav{top:106px !important}body:has(.snowflake-skip-to-content[style*=\"138\"]) #subNav{top:114px !important}body:has(.snowflake-skip-to-content[style*=\"146\"]) #subNav{top:122px !important}.is-hidden .snowflake-person-chip-avatar{display:none}.is-small .snowflake-person-chip-avatar{width:56px;height:56px}.ai-summary ul{margin:16px 0 0 0 !important;padding:0 !important;list-style-type:none}.ai-summary li{margin:0;padding:0 0 0 32px;position:relative}.ai-summary li::before{content:\"\";display:block;border-radius:100%;background:#29b5e8;width:18px;height:18px;position:absolute;top:4px;left:0;border:5px solid #e5f2f7;box-sizing:border-box}.ai-summary li:not(:last-child){margin-bottom:1rem}.snowflake-content-chip-image__image{aspect-ratio:5 / 3 !important}.content-chip-new .snowflake-content-chip-image__image{height:100% !important;aspect-ratio:unset !important}.snapshot-card .snowflake-text p:not(:first-child){margin-top:var(--spacing-01)}.snapshot-card\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:nth-child(2) p:has(b){font-family:'Texta',sans-serif;margin-top:24px}.snapshot-card\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:nth-child(2) p b{font-weight:700 !important}.snapshot-card\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:nth-child(2){border-bottom:1px solid #ccc;padding-bottom:24px}.snapshot-card\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:nth-child(3) p:first-child:has(b){font-family:'Texta',sans-serif;font-size:20px !important;margin-bottom:1rem !important}.snapshot-card\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:nth-child(3) li{display:inline-block}.snapshot-card\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:nth-child(3) li a{display:inline-block;text-decoration:none;padding:4px 16px !important;border:1px solid #ccc;border-radius:24px;color:#666 !important}.snapshot-card\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:nth-child(3) ul{list-style-type:none;display:flex;padding:0 !important;margin:0 !important;gap:12px}.snapshot-card\u003E.container\u003E.cmp-container\u003E.aem-container img{width:90%;max-width:240px;margin:0 auto}.snapshot-card\u003E.container\u003E.cmp-container\u003E.aem-container{padding:40px;max-width:450px;margin:0 0 0 auto;background-color:#fff;box-shadow:0 2px 6px 0 rgba(152,162,179,.25),0 10px 20px 0 rgba(152,162,179,.10);border-radius:8px;border-top:4px solid var(--ui-01)}.ai-summary{background-color:#f3fbfe;border-left:2px solid var(--ui-01);padding:40px}.ai-summary\u003Espan p:last-child:has(i){color:#666;font-size:14px !important}.ai-summary\u003Espan p:last-child:has(i) a{color:#666 !important;text-decoration:underline !important}.ai-summary\u003Espan p:last-child:has(i) a:hover{color:var(--ui-01) !Important}.ai-summary\u003Espan p:first-child:has(b)::after{content:'';display:inline-block;width:20px;height:20px;background-image:url(\"data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M9.3158 3.15226C8.6475 6.2258 6.22698 8.64545 3.15232 9.31587C2.94923 9.36072 2.94923 9.63928 3.15232 9.68413C6.22698 10.3522 8.6475 12.7742 9.3158 15.8477C9.36067 16.0508 9.63933 16.0508 9.6842 15.8477C10.3525 12.7742 12.773 10.3545 15.8477 9.68413C16.0508 9.63928 16.0508 9.36072 15.8477 9.31587C12.773 8.64781 10.3525 6.2258 9.6842 3.15226C9.63933 2.94925 9.36067 2.94925 9.3158 3.15226Z' fill='%23249EDC'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M17.3725 11.5461C16.9098 13.6739 15.2341 15.3491 13.1054 15.8132C12.9649 15.8443 12.9649 16.0371 13.1054 16.0681C15.2341 16.5307 16.9098 18.2074 17.3725 20.3353C17.4035 20.4758 17.5965 20.4758 17.6275 20.3353C18.0902 18.2074 19.7659 16.5323 21.8946 16.0681C22.0352 16.0371 22.0352 15.8443 21.8946 15.8132C19.7659 15.3507 18.0902 13.6739 17.6275 11.5461C17.5965 11.4055 17.4035 11.4055 17.3725 11.5461Z' fill='%23249EDC'/%3E%3C/svg%3E%0A\");background-repeat:no-repeat;background-size:contain;background-position:center;vertical-align:middle;margin-left:8px}.ai-summary\u003Espan p:first-child:has(b){color:var(--ui-01) !important;text-transform:uppercase}.border-top{border-top:1px solid rgba(0,0,0,.2)}.border-top\u003Espan{display:block;padding-top:32px}body .snowflake-card-v2-advanced-image__image{aspect-ratio:16 / 9 !important}.content-chip-new .snowflake-content-chip-image__image{border-radius:0;object-fit:cover;height:100%}.sf-footer #ot-sdk-btn.ot-sdk-show-settings,.sf-footer #ot-sdk-btn.optanon-show-settings{color:rgba(255,255,255,.7) !important;text-underline-offset:4px;border-top:none;border-left:none;border-right:none;border-bottom:1px dotted transparent;background-color:transparent !important;background-image:none !important;transition:300ms ease text-decoration-color;padding:0 !important;font-size:12px;font-family:'Lato',sans-serif}.sf-footer #ot-sdk-btn.ot-sdk-show-settings:hover,.sf-footer #ot-sdk-btn.optanon-show-settings:hover{color:rgba(255,255,255,1) !important;border-bottom:1px dotted var(--ui-01);transition:300ms ease text-decoration-color}.sf-footer__legal-container\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:last-child{flex-shrink:0}.sf-footer__disclaimers{background-color:#042130}.sf-footer__disclaimers .snowflake-simple-stat-disclaimer p a{color:inherit;text-decoration:none !important}.sf-footer__disclaimers .snowflake-simple-stat-disclaimer p sup{margin-right:2px}.sf-footer__disclaimers .snowflake-simple-stat-disclaimer p{text-indent:-5px;padding-left:5px}.sf-footer__disclaimers-inner{border-top:1px solid rgba(255,255,255,.25);padding:40px 0}.sf-footer__disclaimers .snowflake-simple-stat{align-items:flex-start;text-align:left;color:rgba(255,255,255,.7);margin-bottom:10px}.sf-footer__social{display:flex;justify-content:center;gap:12px}.sf-footer .snowflake-footer-social-item{margin:0 !important}.sf-footer .snowflake-footer-social-item a{line-height:0;background-color:rgba(3,24,35,.8);display:inline-block;width:48px !important;height:48px;border-radius:8px;display:inline-flex;justify-content:center;align-items:center;transition:300ms ease background-color}.sf-footer .snowflake-footer-social-item a:hover{background-color:var(--ui-01) !important;transition:300ms ease background-color}.sf-footer__bottom{padding-bottom:40px}.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap .mktoError .mktoErrorMsg{max-width:100%;color:#fff}.sf-footer .mktoForm .mktoError .mktoErrorMsg .mktoErrorDetail{display:inline-block}.sf-footer .mktoFormRow:has(.mktoHtmlText:empty){display:none}.sf-footer .mktoFormRow .mktoHtmlText span{color:#fff !important}.sf-footer{background-color:#042130}.sf-footer .optanon-toggle-display:hover{text-decoration-color:var(--ui-01) !important;cursor:pointer !important;text-underline-offset:4px;text-decoration-style:dotted !important;text-decoration-color:var(--ui-01);color:#fff !important;transition:300ms ease text-decoration-color;text-decoration:underline;opacity:1}.sf-footer__logo{width:40px}.sf-footer-grid__inner\u003E.container\u003E.cmp-container\u003E.aem-container{row-gap:32px}.sf-footer__legal-container\u003E.container\u003E.cmp-container\u003E.aem-container{display:flex;justify-content:space-between;align-items:center;text-align:center;row-gap:16px}.sf-footer__legal-container\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:nth-child(2){text-align:center;flex-grow:1}.sf-footer__legal-links li button,.sf-footer__legal-links li a,.sf-footer__legal-links li{margin:0;color:rgba(255,255,255,.7) !important;font-weight:500}.sf-footer__legal-links li a:hover{color:rgba(255,255,255,1) !important}.sf-footer div.sf-footer__copyright p,.sf-footer div.sf-footer__legal-links li,.sf-footer div.sf-footer__legal-links a,.sf-footer div.sf-footer__legal-links p{font-size:12px !important}.sf-footer__legal-links ul{list-style-type:none;margin:0;padding:0;display:flex;gap:20px;row-gap:4px;justify-content:center;flex-wrap:wrap;text-align:center}.sf-footer__legal-links li:last-child{width:100%}.sf-footer .mktoFormRow:has(.mktoPlaceholder),.sf-footer .mktoFormRow:has(input[type=\"hidden\"]){display:none !important}.sf-footer .mktoFormCol{margin-bottom:0 !important}.sf-footer label[for=\"adhoc1\"]{width:auto !important;flex-grow:1;margin-left:16px}.sf-footer .mktoFieldWrap:has(label[for=\"adhoc1\"]){display:flex;flex-direction:row-reverse;margin-top:22px}.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap .mktoCheckboxList input[type=checkbox]{background-color:transparent !important;border:1px solid rgba(255,255,255,.4) !important;border-radius:4px !important}.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap .mktoEmailField,.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap .mktoTelField,.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap .mktoTextField,.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap select{background-color:transparent !important;color:#fff !important;height:auto !important;border:1px solid rgba(255,255,255,.4) !important;border-radius:4px !important;padding:12px 18px !important}.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap .mktoEmailField:focus,.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap .mktoTelField:focus,.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap .mktoTextField:focus,.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap select:focus{border-color:var(--ui-01) !important}.sf-footer .mktoForm *{padding:0 !important}.sf-footer .mktoForm,.sf-footer .snowflake-marketo-form-container{padding:0 !important;background:transparent;margin-bottom:0;box-shadow:none}.sf-footer .mktoHtmlText.mktoHasWidth{width:100% !important;margin:24px 0}.sf-footer .mktoFormRow{flex-direction:column}.sf-footer .mktoForm .mktoButtonWrap{margin:0 !important}.sf-footer select{background-image:url(\"data:image/svg+xml,%3Csvg width='14' height='8' viewBox='0 0 14 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M.981445 1.43496L6.90897 7.32496L12.9314 1.33496' stroke='white' stroke-width='1.33333' stroke-miterlimit='10' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A\") !important}.sf-footer .snowflake-marketo-form .mktoButtonWrap.mktoNative{justify-content:flex-start}.sf-footer *::placeholder{color:#fff !important;opacity:.8}.sf-footer .mktoForm .mktoButtonWrap.mktoSimple .mktoButton{background-color:var(--ui-01) !important;color:#fff !important;width:100% !important;padding:12px 16px !important;border:1px solid var(--ui-01) !important;background-image:none !important;border-radius:48px;text-transform:uppercase;font-weight:800 !important;font-family:'Texta',sans-serif !important;font-size:16px !important;line-height:1.2}.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap .mktoHtmlText\u003Espan,.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap .mktoLabel\u003Espan,.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap label.mktoLabel{color:#fff !important}.sf-footer__newsletter-title p:not(:first-child){margin-top:8px !important}.sf-footer__newsletter-title p b{font-weight:800 !important;font-family:'Texta',sans-serif !important;font-size:22px !important;line-height:1.2}.sf-footer__newsletter-title p:last-child{font-size:14px !important;opacity:.8}.sf-footer__link-group li a[target=\"_blank\"]::after{content:'';display:inline-block;width:10px;height:10px;margin-left:5px;background-image:url(\"data:image/svg+xml,%3Csvg width='11' height='11' viewBox='0 0 11 11' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.72222 1.22222C6.38471 1.22222 6.11111 .948616 6.11111 .611111C6.11111 .273607 6.38471 0 6.72222 0H10.3889C10.551 0 10.7064 .0643867 10.821 .178988C10.9356 .293596 11 .449032 11 .611111V4.27778C11 4.61529 10.7264 4.88889 10.3889 4.88889C10.0514 4.88889 9.77778 4.61529 9.77778 4.27778V2.08647L4.09879 7.76545C3.86013 8.00409 3.4732 8.00409 3.23454 7.76545C2.99589 7.52681 2.99589 7.13986 3.23454 6.90122L8.91355 1.22222H6.72222ZM0 2.44444C0 1.76943 .547207 1.22222 1.22222 1.22222H4.27778C4.61529 1.22222 4.88889 1.49583 4.88889 1.83333C4.88889 2.17084 4.61529 2.44444 4.27778 2.44444H1.22222V9.77778H8.55556V6.72222C8.55556 6.38471 8.82915 6.11111 9.16667 6.11111C9.50418 6.11111 9.77778 6.38471 9.77778 6.72222V9.77778C9.77778 10.4528 9.23059 11 8.55556 11H1.22222C.547207 11 0 10.4528 0 9.77778V2.44444Z' fill='white'/%3E%3C/svg%3E%0A\");background-size:contain;background-repeat:no-repeat;background-position:center}.sf-footer__link-group ul,.sf-footer__link-group li{margin:0;padding:0;list-style-type:none}.sf-footer__link-group ul{margin-top:20px !important}.sf-footer__link-group li{margin-top:15px}.sf-footer div.sf-footer__link-group\u003Espan\u003Ep\u003Ea,.sf-footer div.sf-footer__link-group\u003Espan\u003Ep{color:var(--ui-01) !important;font-weight:800 !important;font-family:'Texta',sans-serif !important;font-size:20px !important;line-height:1.2}.sf-footer__link-group li a{opacity:.9;color:#fff !important;font-weight:500 !important;font-size:15px !important;line-height:1.3}.sf-footer__link-group li a:hover{opacity:1}.sf-footer-grid__inner\u003E.container\u003E.cmp-container\u003E.aem-container::before,.sf-footer-grid__inner\u003E.container\u003E.cmp-container\u003E.aem-container::after{display:none}.sf-footer__column{flex-grow:1}.sf-footer-grid__inner\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:not(:first-child){width:50%}@media (min-width:800px){.sf-footer__legal-links ul{justify-content:flex-start;text-align:left}.sf-footer__social{justify-content:flex-end}.sf-footer__legal-links ul{padding-left:24px}.sf-footer__legal-container\u003E.container\u003E.cmp-container\u003E.aem-container{text-align:right;flex-wrap:nowrap}.sf-footer__legal-links.align-left ul{justify-content:flex-start}.sf-footer-grid__inner\u003E.container\u003E.cmp-container\u003E.aem-container{display:flex;justify-content:space-between;flex-direction:row}.sf-footer-grid__inner\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:auto !important;max-width:200px}.sf-footer-grid__inner\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:first-child{flex-grow:1;order:2;width:100% !important;max-width:none}.sf-footer__legal-container\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:auto}}@media screen and (min-width:1380px){.sf-footer-grid__inner\u003E.container\u003E.cmp-container\u003E.aem-container{flex-wrap:nowrap}.sf-footer-grid__inner\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:first-child{padding-right:48px;max-width:380px;background-color:rgba(3,24,35,.4);padding:32px;margin-left:48px;border-radius:16px}.sf-footer__link-group li,.sf-footer__link-group li a{font-size:14px !important;line-height:1.3}}@media screen and (max-width:991px){.sf-footer-grid__inner\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv:first-child{order:2;margin-top:24px !important}}@media screen and (max-width:420px){.is-reduced-mobile .heading-1-v2,.is-reduced-mobile .heading-1-v2-sm{font-size:32px;line-height:28px}}.quote-content-chip{background-color:var(--ui-background-05);padding:24px;border-radius:12px;position:relative}.quote-content-chip .black-blue-text-color .snowflake-title-v2-line\u003Espan{color:rgba(0,0,0,.8) !important;font-size:15px !important;line-height:1.5 !important;font-family:'Lato',sans-serif;font-weight:400 !important}.quote-content-chip .black-blue-text-color .snowflake-title-v2-line\u003Espan:not(:first-child){max-width:calc(100% - 200px)}.quote-content-chip .black-blue-text-color .snowflake-title-v2-line\u003Espan:nth-child(2){font-family:'Texta',sans-serif;color:#000 !important;font-size:20px !important;font-weight:800 !important;margin-top:24px}.quote-content-chip .snowflake-content-chip-image{width:140px !important}@media screen and (min-width:992px){.quote-content-chip .snowflake-content-chip-image{position:absolute !important;bottom:24px;right:16px}}@media screen and (max-width:991px){.quote-content-chip .snowflake-content-chip-image{margin-bottom:40px}.quote-content-chip{flex-direction:column}}#spa-root{background-color:#fff}.lowercase .snowflake-title-v2-line{text-transform:none !important}.centered .snowflake-logo-content-container-inner{justify-content:center}div.snowflake-linklist-dropdown-menu{max-height:380px}.first-line-blue .snowflake-typographyv2 .snowflake-title-v2-line:first-child{color:var(--ui-01) !important}.is-front{position:relative;z-index:2}.use-case-body .snowflake-text h1,.use-case-body .snowflake-text h2,.use-case-body .snowflake-text h3,.use-case-body .snowflake-text h4,.use-case-body .snowflake-text h5,.use-case-body .snowflake-text h6{font-family:'Texta',sans-serif;color:#000;margin:.25rem 0 0 0}.pc-hero .button-group\u003E.container\u003E.cmp-container\u003E.aem-container{justify-content:flex-start}.sf-footer .mktoFormRow .mktoHtmlText span{font-family:'Lato',sans-serif !important}.snowflake-button-primary.snowflake-button-blue .snowflake-button-container{justify-content:center}.related-chip-25{background-color:#fff;border:1px solid rgba(204,204,204,.5);border-radius:8px;padding:20px;position:relative}.related-chip-25:hover{box-shadow:rgba(152,162,179,.1) 0 10px 20px 0}.related-chip-25:hover::after{right:24px;transition:300ms ease right}.related-chip-25::after{content:'';display:block;transition:300ms ease right;background-image:url(\"data:image/svg+xml,%3Csvg width='8' height='14' viewBox='0 0 8 14' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M7.66699 7C7.66699 6.6571 7.53559 6.32825 7.30169 6.08578L2.34446 .947072C1.84529 .429617 1.0164 .429617 .517219 .947072C.0427878 1.43887 .042788 2.21798 .517219 2.70978L4.65591 7L.51722 11.2902C.0427889 11.782 .0427887 12.5611 .51722 13.0529C1.0164 13.5704 1.84529 13.5704 2.34447 13.0529L7.30169 7.91421C7.53559 7.67175 7.66699 7.34289 7.66699 7Z' fill='%2329B5E8'/%3E%3C/svg%3E%0A\");width:8px;height:14px;display:block;position:absolute;right:30px;top:50%;transform:translateY(-50%);background-size:contain;background-position:center;background-repeat:no-repeat}.related-chip-25 .heading-5-v2{font-size:22px;line-height:1.1}.related-chip-25 .snowflake-content-chip-image{width:48px;flex-shrink:0}.related-chip-25 .snowflake-content-chip-image__image{aspect-ratio:1;height:auto;object-fit:contain}.related-chip-25 .snowflake-content-chip-button{display:none}.related-chip-25 .snowflake-content-chip-content-without-tag{flex-grow:1;padding-right:24px}.case-study-25.small-logo .snowflake-case-study-card-logo img{width:60px !important}.swiper-slide .case-study-25{width:95%;margin-left:auto;margin-right:auto}.case-study-25 .snowflake-case-study-card-logo img{width:140px !important;height:auto !important;transform:none !important;margin:24px 0 8px 0}.case-study-25 .snowflake-case-study-card-image__image{object-position:left center}.case-study-25 .snowflake-case-study-card-information-container{padding-right:24px}.case-study-25 ul{list-style-type:none;padding:0;margin:8px 0 0 0}.case-study-25 li{font-size:15px !important;line-height:1.3 !important;display:flex;flex-direction:column;border-left:4px solid var(--ui-01);padding-left:24px;margin-top:24px;color:#535862;gap:4px}.case-study-25 li b{display:block;font-family:'Texta',sans-serif;font-weight:900 !important;font-size:48px !important;line-height:.9 !important;color:var(--ui-01)}.case-study-25 .snowflake-case-study-card-description p{color:#535862}.case-study-25 .snowflake-case-study-card-description p:nth-child(2):not(:has(a)){color:#000;font-family:Texta;font-size:30px !important;line-height:1 !important;font-style:normal;font-weight:700;text-indent:-8px}.case-study-25.is-story .snowflake-case-study-card-description p:nth-child(2):not(:has(a)){text-indent:0}.case-study-25 .snowflake-case-study-card-key-card{background-color:transparent}.case-study-25 .snowflake-case-study-card-button{display:none}.case-study-25{border-radius:24px;overflow:hidden}@media screen and (min-width:1024px){.case-study-25 .snowflake-case-study-card-left-container{position:static;width:60%;min-height:0}.case-study-25 .snowflake-case-study-card-right-container::after{content:'';display:block;width:60%;max-width:340px;padding-bottom:50%;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 22 16' class='snowflake-pushdown-banner-placeholder-arrow'%3E%3Cpath fill='%2329B5E8' fill-rule='evenodd' d='M17.865 8.756c.088-.274.124-.555.118-.834a2.551 2.551 0 0 0-1.3-2.142L7.887.76C6.645.055 5.063.475 4.35 1.7a2.535 2.535 0 0 0 .947 3.494l4.916 2.809-4.916 2.801a2.543 2.543 0 0 0-.947 3.502c.713 1.222 2.295 1.64 3.537.934l8.796-5.024a2.541 2.541 0 0 0 1.182-1.46Z' clip-rule='evenodd'%3E%3C/path%3E%3C/svg%3E\");background-size:contain;background-repeat:no-repeat;position:absolute;top:-10%;left:-20%}.case-study-25 .snowflake-case-study-card-right-container{max-width:none;width:40%;position:absolute;top:-5%;right:-5%;z-index:0;height:110%}}@media screen and (min-width:768px){.case-study-25 li{max-width:50%}.case-study-25 ul{display:flex;gap:48px}}.snowflake-text.section-eyebrow p{margin-left:auto;margin-right:auto;margin-bottom:16px !important}.snowflake-text.section-eyebrow p,.snowflake-text.eyebrow-text p{text-transform:uppercase;font-family:'Texta',sans-serif !important;font-weight:800 !important;letter-spacing:.025em;margin-bottom:12px;line-height:1.1 !important}.snowflake-title-v2.dynamic .heading-2-v2 span.snowflake-title-v2-line{font-size:clamp(2.5rem,4.5vw,4rem) !important;line-height:.82 !important}.checklist ul{padding:0;margin:0}.checklist ul li{list-style-type:none;padding-left:32px;position:relative}.checklist ul li:not(:last-child){margin-bottom:1em}.checklist ul li::before{content:'';display:inline-block;width:20px;height:20px;background-image:url(\"data:image/svg+xml,%3Csvg width='24' height='25' viewBox='0 0 24 25' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect y='.985352' width='24' height='24' rx='12' fill='%23D4F0FA'/%3E%3Cpath d='M7.28613 13.2967L10.7147 16.7253L17.5718 9.86816' stroke='%2329B5E8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A\");background-size:contain;background-repeat:no-repeat;position:absolute;top:3px;left:0}.last-line-blue .snowflake-typographyv2 .snowflake-title-v2-line:last-child{color:var(--ui-01)}.snowflake-text p sup{line-height:0}.snowflake-title-v2.lowercase .heading-3-v2{font-size:28px;line-height:1;text-transform:none;font-weight:700}.snowflake-title-v2.lowercase .heading-2-v2{font-size:32px;line-height:1;text-transform:none;font-weight:700}.content-chip-new{border:1px solid rgba(204,204,204,.5);border-radius:16px;overflow:hidden}.content-chip-new .snowflake-image-container{border-radius:0;display:none}.content-chip-new .snowflake-content-chip-image{margin-right:0;max-width:180px;flex-shrink:0}.content-chip-new .snowflake-content-chip-content{padding:24px}.content-chip-new .black-blue-text-color .snowflake-title-v2-line:first-child{font-size:24px;line-height:1.1}.content-chip-new .black-blue-text-color .snowflake-title-v2-line:not(:first-child){font-family:'Lato',sans-serif;font-size:17px;color:#535862 !important;font-weight:500;line-height:1.45;margin-top:8px;display:none}div.snowflake-text a{font-weight:normal;color:var(--ui-01);text-decoration:underline;text-underline-offset:4px;text-decoration-style:dotted !important;text-decoration-color:transparent;transition:300ms ease text-decoration-color}div.snowflake-text a:hover{text-decoration-color:var(--ui-01);transition:300ms ease text-decoration-color}.footer-nav__link-group .snowflake-button-container,.subnav__item--button,.snowflake-card-v2-advanced-button .snowflake-button-container{justify-content:flex-start}.button-container\u003E.container\u003E.cmp-container\u003E.aem-container{align-items:center}.button-container\u003E.container\u003E.cmp-container\u003E.aem-container .snowflake-button-primary+.snowflake-button-link{margin-left:12px !important}.snowflake-button-regular.snowflake-button-link .snowflake-button-container{font-size:18px !important;text-align:left;justify-content:flex-start;line-height:1.4 !important}body .snowflake-card-v2-advanced{border:1px solid rgba(204,204,204,.5);border-radius:var(--spacing-02);transition:300ms ease all}body .snowflake-card-v2-advanced:hover{box-shadow:rgba(152,162,179,.1) 0 10px 20px 0;transition:300ms ease all}body .snowflake-card-v2-advanced-inner{border-bottom:none}body .snowflake-card-v2-advanced-image{line-height:0}body .snowflake-card-v2-advanced-image__image{aspect-ratio:16 / 9}body .snowflake-card-v2-advanced-content{position:relative}body .snowflake-card-v2-advanced-content::after{content:'';display:block;position:absolute;bottom:0;left:0;transition:300ms ease all;width:20%;height:4px;background-color:var(--ui-01);opacity:0}body .snowflake-card-v2-advanced:hover .snowflake-card-v2-advanced-content::after{width:100%;opacity:1;transition:300ms ease all}body .snowflake-card-v2-advanced .snowflake-button-link.snowflake-button-blue .snowflake-button-container\u003E.link-icon{transition:300ms ease transform}body .snowflake-card-v2-advanced:hover .snowflake-button-link.snowflake-button-blue .snowflake-button-container\u003E.link-icon{transform:translateX(4px);transition:300ms ease transform}.six-columns\u003E.container\u003E.cmp-container\u003E.aem-container,.three-columns\u003E.container\u003E.cmp-container\u003E.aem-container,.four-columns\u003E.container\u003E.cmp-container\u003E.aem-container,.five-columns\u003E.container\u003E.cmp-container\u003E.aem-container{display:flex;flex-wrap:wrap;gap:24px}.six-columns.align-center\u003E.container\u003E.cmp-container\u003E.aem-container,.three-columns.align-center\u003E.container\u003E.cmp-container\u003E.aem-container,.four-columns.align-center\u003E.container\u003E.cmp-container\u003E.aem-container,.five-columns.align-center\u003E.container\u003E.cmp-container\u003E.aem-container{justify-content:center}.three-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:100%;margin:0 !important}.six-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv,.four-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv,.five-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:calc(50% - 12px);margin:0 !important}@media screen and (min-width:768px){.three-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:calc(50% - 12px)}.six-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv,.four-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv,.five-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:calc(33.333% - 16px)}}@media screen and (min-width:1024px){.snowflake-title-v2.lowercase .heading-3-v2{font-size:34px}.snowflake-title-v2.lowercase.larger .heading-2-v2{font-size:44px;line-height:.95}.three-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:calc(33.333% - 16px)}.four-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:calc(25% - 18px)}.five-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:calc(20% - 19.2px)}.six-columns\u003E.container\u003E.cmp-container\u003E.aem-container\u003Ediv{width:calc(16.6666% - 20px)}.snowflake-title-v2.lowercase .heading-3-v2{font-size:28px !important}}@media screen and (min-width:1200px){.snowflake-title-v2.lowercase .heading-2-v2{font-size:40px}.content-chip-new .snowflake-content-chip-content{padding:32px}.content-chip-new .snowflake-image-container,.content-chip-new .black-blue-text-color .snowflake-title-v2-line:not(:first-child){display:block}}.promo-banner-25{border-radius:16px;overflow:hidden}.promo-banner-25 .snowflake-premium-content-banner-image-container{position:relative;max-width:380px}.promo-banner-25 .snowflake-text{color:#535862}.promo-banner-25 .snowflake-premium-content-banner-image__image{transform:translateY(8px);transition:300ms ease transform;border-radius:0;width:85%;margin:0 auto;display:block;position:relative;z-index:1}.promo-banner-25 .snowflake-premium-content-banner-image__link:hover .snowflake-premium-content-banner-image__image{transform:translateY(0);transition:300ms ease transform}.promo-banner-25 .snowflake-premium-content-banner-image__inner{height:auto;padding-top:24px}.promo-banner-25 .snowflake-premium-content-banner-image__link{position:relative;z-index:1;height:auto}.promo-banner-25 .snowflake-premium-content-banner-image__link::after{content:'';display:block;position:absolute;clip-path:polygon(0 0,66% 0,100% 100%,0 100%);bottom:0;left:0;width:100%;height:100%;background:var(--ui-01);transition:300ms ease width}.promo-banner-25 .snowflake-premium-content-banner-image__link:hover::after{width:110%;transition:300ms ease width}.sf-footer .snowflake-marketo-form .mktoFormRow .mktoFieldWrap select{background-position:95% 50%}.sf-footer__disclaimers .text-size-small .snowflake-text p{color:#fff !important;font-size:10px !important;opacity:.8}@media screen and (min-width:768px){.sf-footer__disclaimers .text-size-small .snowflake-text p{font-size:12px !important}}@media screen and (max-width:1023px){.mobile-top-padding{padding-top:64px}}@media (max-width:799px){.sf-footer .snowflake-marketo-form .mktoButtonWrap.mktoNative .mktoButton{width:100% !important}.sf-footer__logo{text-align:center;display:block;margin:0 auto}}.customer-card .snowflake-card-v2-advanced-image{aspect-ratio:4.35 / 1}.customer-card .snowflake-card-v2-advanced-image__image{width:100%;height:100%;padding-left:8px;object-fit:contain;object-position:left center;margin:0 !important;aspect-ratio:initial}.customer-card .snowflake-card-v2-advanced-image__inner{height:110px}.customer-card .snowflake-card-v2-advanced-tag-indicator{display:none}.pc-hero .snowflake-container-arrow-small-gray-image{top:-34% !important;width:18% !important}.pc-hero .snowflake-container-arrow-small-gray-image path{fill:var(--ui-01);opacity:1}@media screen and (max-width:767px){.mobile-padding-top{padding-top:64px}.hide-mobile{display:none !important}.pc-hero{padding-top:52px}.pc-hero .snowflake-text p,.pc-hero .left-alignment .snowflake-title-v2-line,.pc-hero h1 span{text-align:center !important}}div.snowflake-pushdown-banner-button{margin-top:0}.button-group.align-center\u003E.container\u003E.cmp-container\u003E.aem-container{align-items:center;justify-content:center !important}.text-center .snowflake-breadcrumb-swiper .swiper-wrapper{justify-content:center}div.snowflake-breadcrumb a.snowflake-breadcrumb-item,.snowflake-breadcrumb div.snowflake-breadcrumb-item{text-transform:none;font-weight:500}.snowflake-breadcrumb svg{display:none !important}.snowflake-breadcrumb a:has(svg)::after{content:'/';margin:0 12px;color:#666}.hide-filters .snowflake-filterable-and-searchable-grid-top-part{display:none !important}.page-section{padding-left:24px;padding-right:24px}@media screen and (min-width:768px){.page-section{padding-left:48px;padding-right:48px}}.download-card pre[class*=language-]{overflow-x:scroll !important}","isGSAPEnabled":false,":type":"snowflake-site/components/markup-editor"}},":itemsOrder":["container_copy","container_573483281_","markup_editor_copy"]}},":itemsOrder":["root"],"classNames":"aem-xf"},"markup_editor":{"id":"markup-editor-02953e685c","title":"Quickstarts Overrides","cssContent":".snowflake-markdown blockquote{padding:24px 32px;background:#f6f9fa;border:1px solid #29b5e8;border-radius:16px}.snowflake-markdown .snowflake-image-container img{width:auto !important;max-width:100%}.snowflake-markdown .snowflake-text ol{padding-left:20px !important}.snowflake-markdown .snowflake-text li{margin:0 0 12px 0 !important}.snowflake-markdown h3.snowflake-markdown-h3{font-size:20px !important;font-family:Texta,sans-serif !important}@media (min-width:768px){.snowflake-markdown h3.snowflake-markdown-h3{font-size:28px !important}}","isGSAPEnabled":false,":type":"snowflake-site/components/markup-editor"}},":itemsOrder":["experiencefragment-banner","experiencefragment-header","markup_editor_1950346551","responsivegrid","modal_container","experiencefragment-footer","markup_editor"],":type":"wcm/foundation/components/responsivegrid"}},":itemsOrder":["root"],":hierarchyType":"page",":path":"/content/snowflake-site/global/en/developers/guides/geo-for-machine-learning","locale":"en"}
  