Giter Site home page Giter Site logo

canadian-geospatial-platform / geoview Goto Github PK

View Code? Open in Web Editor NEW
22.0 7.0 31.0 49.98 MB

Canadian Geospatial Platform Viewer / Visualiseur de la Plateforme Géospatiale Canadienne

License: Other

HTML 16.42% CSS 0.15% TypeScript 82.09% JavaScript 1.31% Shell 0.03%
web-mapping geospatial openlayers6

geoview's Introduction

GeoView

The Canadian Geospatial Platform intends to deploy new infrastructure, tools and web integration of GeoCore, a new geospatial metadata lake library capable of supporting multiple metadata standards. In recognition of these desired capabilities, it needs a lightweight viewer to incorporate in their infrastructure. The need is to have flexible viewer to display geospatial data from GeoCore metadata lake on a map with customizable functionalities.

Demo

BigPicture

Solution

GeoView mapping capabilities are based on OpenLayers open source viewer. The overall project uses the latest React framework version 17+. With this in mind, here is the list of the main dependencies

Project Structure

This project is now a monorepo and contains the following packages under the packages folder:

  • geoview-core - the core is responsible for managing APIs, layers, user interface and rendering the maps. The core will also expose API access to be used in internal, external packages and apps that uses the viewer.

  • geoview-details-panel - a package that displays a panel with details when a location / feature is clicked on the map.

  • geoview-basemap-panel - a package that displays a panel with a list of basemaps that can be selected to switch the map's basemap.

  • geoview-layers-panel - a package that displays a panel with a list of loaded layers and their legend.

  • geoview-swiper - a package that enable a swiper control to tooggle visibility of layers from one side to the other side of the swiper bar.

Development

Our developers use Visual Studio Code with a few extentions to help linting and formatting

  • Prettier - Code formatter

  • ESLint

  • Better Comments

  • We are using React Dev Tools

Documentation for GeoView

  • click here to view generated typedoc for the GeoView core.
  • click here to view best practicies and how to develop with GeoView.

Contributing to the project

see our contributing guide

Building the project

First clone this repo

$ git clone https://github.com/Canadian-Geospatial-Platform/geoview.git

Go to the directory of the cloned repo

$ cd geoview

Install rush globally

$ npm install -g @microsoft/rush

Install dependencies

It's always recommended to run the below command if you pull any changes.

$ rush update

If you need to re-download the modules you can run

$ rush update --full

Build the project:

$ rush build:core

Output build files will be placed under

packages/geoview-core/dist

Run/Serve the project

$ rush serve

GeoView will be serve from http://localhost:8080/

Deploy to gh-pages

Build the project:

$ rush build:core

Push the dist folder to your gh-pages

$ rush host

The project will now serve inside your GitHub gh-pages at

https://[GITHUB-USERNAME].github.io/geoview/index.html

Make sure GitHub pages are active inside your origin repository

Usage

We'll go through the simplest way to use the Canadian Geospatial Platform Viewer.

Using the viewer on your own project

For the moment, the released bundle of the viewer is hosted under:

https://canadian-geospatial-platform.github.io/geoview/public/cgpv-main.js

As the viewer is still in development, this bundle will always contain the latest commits.

To use the viewer on your own project, you need to include the above script in a script tag in the header of your HTML file

<!DOCTYPE html>
<html>
  <head>
    <script src="https://canadian-geospatial-platform.github.io/geoview/public/cgpv-main.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

After including the viewer in your page, the viewer will allow you to load maps and draw them on your page.

There are multiple ways to load maps. Below we will show a basic usage of loading a map, if you want to see how you can load the map in all supported ways then click here.

Loading a map using a config passed in as inline to the map div element

The viewer allows you to load multiple maps on the page, you need to provide a different id for each map. Maps are added in the body tag of the HTML document. You can also load maps inside any JS framework such as React, Angular, VueJS.

For the viewer to recognize that you are trying to render a map on the page, you need to have a div element with class "geoview-map".

It's recommended to pass in an id attribute, if an id is not passed then the viewer will auto generate an id for you. If you want to use APIs that control this map then you will need to view all created maps on the page and figure out the id of the created map.

Tip: to view all maps on the page you can console out the maps using this function `console.log(cgpv.api.maps)

Below is an example of a simple map, with an id mapOne. This map will be using LCC projection (EPSG:3978) and will have a zoom of 4, a center of 60 latitude and -100 longtitude. The interaction of the map will be dynamic (meaning that you can move around and zoom in/out). It will use the transport, shaded with labels as the basemap. It will display an esri dynamic layer with multiple sub layers. The language of the map will be English.

<div
  id="mapOne"
  class="geoview-map"
  style="height: 100vh;"
  data-lang="en"
  data-config="{
      'map': {
        'interaction': 'dynamic',
        'viewSettings': {
          'projection': 3978
        },
        'basemapOptions': {
          'basemapId': 'transport',
          'shaded': true,
          'labeled': true
        },
        'listOfGeoviewLayerConfig': [
          {
            'geoviewLayerId': 'esriDynamicLYR2',
            'geoviewLayerName': {
              'en': 'Energy',
              'fr': 'Energy'
            },
            'metadataAccessPath': {
              'en': 'https://maps-cartes.ec.gc.ca/arcgis/rest/services/CESI/MapServer',
              'fr': 'https://maps-cartes.ec.gc.ca/arcgis/rest/services/CESI/MapServer'
            },
            'geoviewLayerType': 'esriDynamic',
            'listOfLayerEntryConfig': [{ 'layerId': '0' }, { 'layerId': '6' }]
          }
        ]
      },
      'theme': 'geo.ca',
      'components': ['north-arrow', 'overview-map'],
      'corePackages': []
    }"
></div>

Once you add the above to the body of the html file. You must call the init function to allow the viewer to render the map.

<script>
  // init functions, takes one parameter as a function callback. Any code inside the callback will run once map has finished rendering.
  cgpv.init(function () {});
</script>

Full example:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://canadian-geospatial-platform.github.io/geoview/public/cgpv-main.js"></script>
  </head>
  <body>
    <div
      id="mapOne"
      class="geoview-map"
      style="height: 100vh;"
      data-lang="en"
      data-config="{... insert your configuration ...}"
    ></div>
    <script>
      // init functions, takes one parameter as a function callback. Any code inside the callback will run once map has finished rendering.
      cgpv.init(function () {});
    </script>
  </body>
</html>

geoview's People

Contributors

alex-nrcan avatar amir-azma avatar codey-codes avatar cphelefu avatar cphelefudk avatar damonu2 avatar dayc-mapdev avatar dennisfu avatar dependabot[bot] avatar guichoquette avatar jolevesq avatar jvanulde avatar kaminderpal avatar kenchase avatar larrywang888 avatar malvoz avatar maxmalynowsky avatar mayurmarakana89 avatar melzeiny1980 avatar yass0016 avatar ychoquet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

geoview's Issues

Add north arrow

Add north arrow component. Very useful when you are in LCC projection because north is not always on top.

Create custom reusable panel

Create a custom reusable panel component to be reuse by the other components.

We should have basic panel

  • App bar panel
  • Modal window
  • Customizable size panel (trigger from outside with the api)
  • Link panel (the div is pass to the function and the viewer will create the panel from it). It will be outside the viewer div

Add details panel

Create a panel to hold the get info when a user click on the map. The return value may contain multiple layers and multiple features for each of the layers. We need to manage both.

Do not add internal field to the panel like SHAPE or OBJECTID

Make it WCAG. If not done, start an issue for WCAG to have something like in the current viewer for clicking the map

Fullscreen not working on IPhone and IPad

From IPhone and IPad in Chrome, the fullscreen doesn't work at all.

For IPad in Safari it works but when we zoom out the map with 2 fingers, it goes out of full screen.

We are using screenfull npm module. Look at configuration or other libraries if it can solve the problem.

Support Vector Tile

Refactor code for layers and vectors

Currently we are adding the layers in map.tsx, find a better way to create them, also initialize a layer object in map-viewer to call layer functions from the API

Refactor vectors to add a function to instead of deleting the gorup and unregistering it, create a function to delete the layers from the group and a function to delete and unregister the group

  • better way to create the layers from the configs in map.tsx
  • initialize layer in map-viewer to call layer functions from the api
  • add a separate function to delete layers from the group

Do load test

Leaflet is a light weight library to do mapping. It works very well with light layer where the feature are simple (not too many vertices) and when there is not too much of them.

We need to do load test with different layer (WMS, ESRI dynamic and feature) to see what are the limits
We need to do load test by adding simples geometries and see what are the limits

From the result, we need to update our documentation so we can inform user of what is the best practice or even prevent the loading of a layer who would freeze the viewer.

App bar focus on panel open

When a panel opens from app bar (and any other panel), focus should be set on the open panel close button. This piece of code should be added at the custom panel component level (#6) so all implementation of panel will have it set.

At the same time, on creation this panel should receive a reference to it's caller so when the panel close, the focus is set on the caller.

Basemap switcher

Add a basemap swticher to the viewer. This basemap should use our default basemap and let the user switch between them. It should be made as part of a plugin.

As a plugin it may have the capacity to add basemap who are not default to the map.

We will leverage our panel plugin approach with this basemap switcher. it will be consider as a core-plugins. We will need a configuration. Below, it is the actual config for the RAMP viewer. We may use this as a starting point:

"baseMapNode": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "string",
                    "description": "A unique identifier for the basemap"
                },
                "name": {
                    "type": "string",
                    "description": "Name of the basemap used for labeling"
                },
                "description": {
                    "type": "string",
                    "description": "Description of the basemap. Will be visible when basemap selector is expanded.Description of the basemap. Will be visible when basemap selector is expanded."
                },
                "typeSummary": {
                    "type": "string",
                    "description": "Optional base map type. This is descriptive only, and will be shown in the basemap selector."
                },
                "altText": {
                    "type": "string",
                    "description": "Alt text for the basemap thumbnail image."
                },
                "thumbnailUrl": {
                    "type": "string",
                    "description": "Path to image file to display in the basemap selector."
                },
                "tileSchemaId": {
                    "type": "string",
                    "description": "The tile schema for this basemap (should reference map.tileSchemas.id)"
                },
                "layers": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string",
                                "default": ""
                            },
                            "layerType": {
                                "type": "string",
                                "default": ""
                            },
                            "url": {
                                "type": "string",
                                "default": ""
                            },
                            "opacity": {
                                "type": "number",
                                "default": 1,
                                "description": "Initial opacity"
                            }
                        },
                        "additionalProperties": false,
                        "required": ["id", "layerType", "url"]
                    },
                    "minItems": 1,
                    "description": "A set of URLs which should be composited to form a basemap entry"
                },
                "attribution": {
                    "$ref": "#/definitions/attributionNode"
                },
                "zoomLevels": {
                    "type": "object",
                    "properties": {
                        "min": {
                            "type": "number"
                        },
                        "max": {
                            "type": "number"
                        }
                    },
                    "default": {},
                    "additionalProperties": false
                }
            },
            "required": ["id", "name", "description", "layers", "altText", "tileSchemaId"],
            "additionalProperties": false
        },

Create the ability to load a plugin using a react component

Right now creating a plugin to load content from outside such as creating a content for a panel is done by passing HTML elements, add the ability to create the content using a react component so that if a change happens in the state the content will update automatically

Improve app startup component selection

For the moment, the app starts when the page load. The script will loop trough the divs with class 'llwb-map'. Those divs contains configuration like projection, zoom and center point, language, layers....

We need to add 2 other type of loading

Load from url config
When the page load, it reads the url for configuration and apply it the div with 'llwb-map' on the page. The url will contains the same type of information https://....ca?b=3857&z=4&c=50;-75&keys=45we-54sf-r25w-52ee;.....

  • b for the basemap projection, z and c for the initial extent
  • Keys will be a list of layer uuid to add. The viewer will tehn call the catalog to get all the needed configuration to add all the layers listed in the keys element.
  • The language will be derive from the page
  • Only layers inside Web Presence catalog can be added this way

Load from a function call
Call the app and pass the needed configuration and div on the map to create the map inside. The configuration will not accept keys. It will be as the same format as the one provided trough the inline div configuration or the configuration file schema.

Load from a configuration file
Instead of having a JSON object in data-leaflet. have a url to link to a JSON file who holds the configuration. The config from the file may be more complex and follow a schema.

<div
    id="mapLCC"
    class="llwp-map"
    data-leaflet="https:\\.....\my-config-fr.json"></div>

Use one render function as a parent level

Instead of rendering maps separately each as its own parent, create a render function that will render the maps as child components. This will help with sharing state and context.

Make the UI responsive

Make sure we have a nice transition from mobile to desktop using threshold for small, medium and large device. Large device should include everything. When we downsize we start to remove/modify some of the components

Large:

  • Nav bar
  • Scale
  • Mouse position
  • Attribution
  • App bar
  • Overview map

Small and Medium:

  • Nav bar
  • Scale
  • App bar

The threshold should be part of theme. So this issue is related with #3

Create a function to generate an ID for layers when added

Create a reusable function that will generate an id when creating a new layer. Also add a name property to the LayerData interface so we can manage layers with same name, getting layer ids from name, removing multiple layers with same name. To test this add a remove button to remove the added layer using id or name.

  • generate id function
  • add name property to LayerData and layer config array in the inline html
  • add remove button to remove layer by id or name

Update Readme

Update readme with
[ ] Deploy
[ ] Install and launch
[ ] Contribution

Attribution is getting too long

Attribution at the bottom of the map is getting too long when we add layers who add their attribution inside this room. The result is the coordinates and scale section is written over. Possible solution:

  • Put a max width to the div so there is no overlap
  • Remove attribution added by layers

Add simple core basemap switcher

We should have a core basemap switcher to use our default basemap. The switcher will be able to even switch projection from LCC to WM.

Add Geosearch

Add the ability to search and zoom by geographic extent. By default we should use geonames like what we currently have inside the FGP viewer (http://fgpv-app.azureedge.net/demo/tags/v3.2.0/dev/samples/index-samples.html). Once zoom (optional from config) a graphic can be draw on the map to show the extent.

We also want it to customizable so other type of libraries can be use. A student did a a proof of concept. It can be a starting point: https://github.com/s26bell/plugins/tree/geolocator/geolocator. We would like to integerate address API like StatCan and custom data from S3 buckets.

The search should be filterable by map extent and by category. It should support as well Scale and lat/long position.

Event on selection should be expose to the outside so the component may be reuse in other applications.

Issues will be created and added to this epic as needed.

Take a look at

Sample for OpenLayers: https://github.com/jonataswalker/ol-geocoder

Create a custom theme

Create a custom theme for the application to reuse in the different components (https://material-ui.com/customization/theming/)

[ ] Palette
[ ] Typography
[ ] Spacing
[ ] Breakpoints for UI
[ ] z-index
[ ] Globals

We can have any values for some of the classes like we do care which typo we will use for the moment. Pick one and we will modify later. The important thing here is to have those values inside a global theme and this theme will be use trough the application. When we will modify the theme later, the values will apply automatically to the element.

Part of this issue or may be extract on is own, we will need documentation page to explain how to use it inside the component.

Add support for GeoJSON

Add support for adding geometries from GeoJSON format.

For the moment, all layers are in the same class. May be a good idea to have a parent class and every type of layers will inherits from that class. Having GeoJSON in is own class will be a good start. For this first phase we need to
[ ] Show GeoJSON on map
[ ] Apply style

Add predefine theming for polygon

Add few predefine style option in our theme. User may pass the style name to create a geometry instead of a full options. We should few marker option as well.

Related to #47

App bar

Try to make the app bar more customizable. Maybe try to remove the Git icon at the bottom and only show version when bar is expand. Take a look at the app bar of Google earth

Create function to add vector to a map

We need the ability to add

  • Polylines
  • Points (circle)
  • Polygons

Those geometries may be added in different projections (projection issues)
Those geometries needs to be added as layer group or feature group

The functions needs to be part of the api so we can create from inside the viewer and also from the outside.

On the sample page, create a button with a text box where we can add geometry coordinates to add vector on the map

Find a way to make API calls after maps are ready

Currently we can make API calls from outside our app (specifically from index.html at the moment)
The problem is that some API calls requires a leaflet map instance to be ready and we are creating this map instance using react leaflet when we render MapContainer
The issue is that rendering is taking few seconds and running API calls will be called earlier and cause an error or return undefined because they are being called on an undefined object since the Map is not ready yet.
We need to find a way to make those API calls after the map is ready

Create event types

Create event type for map, layer, legend, panel to seperate them from one large constant in api/event.ts

Create plugin load

Make the viewer as an API who accept plugin to load dynamically some tools. Use Geo core search engine as the first plugin to work.

There will be 2 ways to link a plugin. One is a simple react component js file outside the project for simple plugin. The other is a bundle package plugin .

Add AWS amplify

The project use the deploy to gh-pages for PR review. We also would like to add AWS amplify to deploy to the cloud for our developement branch.

https://www.npmjs.com/package/aws-amplify

We already have deploy key in our repo (setting - deploy key)

Use GitHub Action to trigger the event

Document processes

Write documention for

  • Contributing guide line
  • How to add a new panel to app bar
  • How to add a new button to nav bar
  • How to startup the app
  • How to configure language
  • High level architecture (encapsulation) and development guide line
  • Configuration schema
  • Style guide to use the custom application theme
  • Testing procedure
  • Supported functions, layer types

Each documentation piece will be part of this epic

Create projection capabilities

For the moment, we are using proj4 to manage our projection. We support Web Mercator and LCC. We need a function to project a geometry or array of geometries
[ ] LCC to WM
[ ] WM to LCC
[ ] Lat/Lng to WM or LCC
[ ] WM or LCC to LatLng

Those functions needs to be accessible from inside the viewer and the outside with the api

At the same time, to validate the concept, make the geoJSON file loads in the LCC map.

Shaded releif and simple basemap

Add the shaded relief basemap to our array of default basemap. At the same time change the way it is done right now. Not only use the code but also the name e.g. 3857-CBMT, 3978-CBMT, 3978-SHADED, 3978-SIMPLE.

Create a third map on the sample page to showcase the new basemap shaded releif.

Overview map init

Overview map is not initialize properly for LCC map. The rectangle for extent is not present and the zoom level is wrong. Moving the map reset the value for the overview map and make it works as expected.

Find a way to initialize it properly.

Add Legend - Custom Legend

Add a legend plugin to help user navigate the content of a map. This legend must have at least the following functions:

  • Show/hide layer, layer group, all
  • Expand/collapse layer, layer group, all
  • Add layer (may be in his own Epic), just add a placeholder
  • Set transparency
  • Show and expand/collapse symbology for a layer
  • Show layer, layer group name
  • Remove layer, layer group
  • Zoom to layer, layer group boundary, zoom to scale
  • Show layer, layer group bounding box
  • Show - Hide WMS layers on layer basis (like ESRI dynamic)
  • Manage exclusive visibility set (radio button)
  • Change style for layers who support multiple style like WMS
  • Open datable
  • Metadat link???
  • Description???
  • Config plugins???
  • Gather other requirements

Issues will be created and added to this epic as needed.

Will be based on https://jolevesq.github.io/contributed-plugins/swiper/samples/swiper-index.html?sample=1

As we do this Epic, make sure UI, UX is in synch with #219

Map vector/geoJSON feature style

Because GeoJSON and vector added to the map doesn't have symbology all the time we will need a class to hold styling these elements.

As part of this issue, modify the layers/geojson.ts file to make it work with the new class.

When a vector or GeoGSON is added to the map it may have an optional value for the symbology to apply. If not, the viewer should apply a value. These values should be inside our theming file or a file related to it. We should support 5-10 different symbol who will be apply one after the other. More then that we just create a custom color (who is not part of the one we use)

Improve gitignore file

Add missing files to gitignore.

We should remove:

  • bundle analyzer.html and bundle-size-analyzer.log. There are created by the build process.
  • .DS_Store created by Mac
  • ...

Create configuration class

Because the viewer can be launch from a configuration object, we should have a class to validate the object then call the proper function. Those functions should be part of the api so we can add layer from outside as well. For the moment configuration is very basic. From what is inside
[ ] zoom level
[ ] center
[ ] layers
[ ] projection code

Should be validate (e.g. projection code 3857 or 3978) and map.tsx call this config class to get the value to apply. Later, all this info will come from the url. A parser will extract the info and populate this class properties.

Create global state management

Create global state management so apps can share information between each other or with other application like a dashboard trough API functions. Also this global state management will be use inside one map (app) to store state of the map so every component knows what is going on and the state can be share to reinitialize another app with the same state.

Use React Context
https://tyrannosaurustech.com/blog/global-state-management-with-react-hooks-and-context/
https://dev.to/ryanmoragas/global-state-in-react-2kcp

This context needs to used carefully, we do not want all component to be able to modify the state value. Only the creator of that state should be able to modify the state. Other subscriber can consult the value.

Few use cases

  • With this, a map can listen to an extent change from another map and then zoom to that extent.
  • A dash board component from another app query the state to know features value from a layer to create a chart
  • State from a map (basemap, extent, loaded layers with display parameter, user annotation, ...) can be share to another app to reproduce the same map

List of state / events to follows

  • Initial configuration
  • Loaded basemap and projection
  • View extent and zoom Level
  • Loaded layer with visibility and opacity setting
  • Subscribe to extent change (moveEnd, zoomEnd)
  • Open panel and active tool
  • Map language

Manage drawer close from Panel and Button

At the moment, the closeDrawer function is manage by every child of appbar button and implementation of appbar panel. For example, appbar/buttons/layers.tsx and layers/layers-panel.tsx are holding logic to close the panel when the appbar drawer opens.

We need to have this at a higher level. appbar/panel.tsx and appbar/buttons.tsx should the logic so when we add a new button/panel inside our core or later from outside plugins, those implementation do not have to care about referencing this behaviour.

Create sample pages

Create different sample pages to showcase our different use cases

Use Cases

  1. Use it as map display from metadata web presence page to show layer extent
  2. Use it as map display from metadata web presence page to show layer like a thumbnail
  3. Use it as geo search tool to do a spatial search for metadata from geoCore API
  4. Use it as simple viewer to with legend to explore data

Functionalities

Use case 1: Show extent
a. LCC basemap and projection
b. Add graphic for the extent

Use case 2: Show layer like thumbnail
a. LCC or Web Mercator basemap and projection
b. Add layer (GeoJson, WMS, Esri (dynamic and feature)

Use case 3: Geo Search
a. LCC or Web Mercator basemap and projection
b. Zoom in, Zoom out, Fullscreen
c. Scale, Mouse position, Overview map
d. Get extent from map extent (add draw later)
e. Link to Geo Search API to add results
f. Bilingual and WCAG

The remaining use case would need more development before creating a functional use cases
Use case 4: Simple viewer
a. LCC or Web Mercator basemap and projection
b. Zoom in, Zoom out, Fullscreen
c. Scale, Mouse position, Overview map
d. Legend (visibility, opacity, graphic legend)
e. Bilingual and WCAG

Add export

Add the ability to export the map in different format

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.