[FEATURE-723] Perspective map GeoJSON doesn't accept style

It should already be part of 8.1.14. Although, you’ll want the latest nightly that fixed a regression related to missing position properties in vector and marker click events. We also added some other features we hope you’ll find useful.

Summary of changes:

The configuration possibilities of the Map components GeoJSON layers has been expanded to allow style options for GeoJSON feature objects. A feature object has a “type” property with a value of “Feature”. If you wish to familiarize yourself with the GeoJSON schema specifications, you can find them here.

The default schema of Polygon, Polyline, Rectangle, and Circle layers has been changed to align with how GeoJSON features are configured, and to help make configuration more straight forward.

The vector and marker click events now get passed a dictionary parameter called “properties”.

GeoJSON layer

  • A styleOptions property can be added to configure style related options of feature objects

    • Add styleOptions at the layer level for shared style options among features
    • Add styleOptions at the feature level to override any shared style options, and apply feature specific style options
    styleOptions: {
        "stroke": true,
        "color": "",
        "weight": 1,
        "opacity": 1,
        "lineCap": "inherit",
        "lineJoin": "inherit",
        "dashArray": "",
        "dashOffset": "",
        "fill": true,
        "fillColor": "",
        "fillOpacity": 1,
        "fillRule": "inherit",
        "interactive": true
    }
    
  • GeoJSON feature objects that are rendered on the map are subscribed to click events, triggering vector and marker click events

  • The properties member object of a feature object is a supplied parameter to vector and marker clicked event handlers

  • Point feature objects, which Leaflet renders as markers by default, accept special configuration

    • May be rendered as a circle marker or icon marker based upon the value of the feature object’s render property

      marker: {
          render: "circle"
      }
      
    • May be configured to show a basic text tooltip, with various options equivalent to Leaflet’s tooltip options here

      marker: {
          tooltip: {
              content: {
                  text: "Forest Products Laboratory"
              },
              options: {
                  direction: "auto",
                  permanent: false,
                  sticky: true,
                  interactive: false,
                  opacity: 0.9,
                  className: ""
              }
          }
      }
      
    • May be configured to show a basic text popup, with various options equivalent to Leaflet’s popup options here

      marker: {
          popup: {
              content: {
                  text: "1 Gifford Pinchot Drive"
              },
              options: {
                  maxWidth: 300,
                  minWidth: 50,
                  maxHeight: null,
                  autoPan: true,
                  keepInView: false,
                  autoClose: true,
                  closeOnEscapeKey: true,
                  className: ""
              }
          }
      }
      
    • Can be configured even further with marker specific options, mostly equivalent to Leaflet’s marker and circle marker options here

      marker: {
          options: {
              opacity: 1,
              riseOffset: 0,
              riseOnHover: false,
              draggable: false,
              clickable: true,
              radius: 10
          }
      }
      

The Map components GeoJSON layers are not meant to be modified directly via the property editor. It is the expectation that designers will make use of an HTTP binding and a map transform to add these configurations as foreign members (object member properties not part of the GeoJSON schema spec) to their layers and features that we process and pass along to the Leaflet API. Here is an example of a map transform on the returned response of an HTTP binding:

```
def transform(self, value, quality, timestamp):
    from com.inductiveautomation.ignition.common.script.adapters import PyJsonObjectAdapter
    from org.python.core import PyArray
    
    # This function is how the Map component and Leaflet traverses and determines if an GeoJSON object qualifies as a Feature object.  It is generic.  If you know 
    # what the layer looks like, i.e. a FeatureCollection of Point objects, you definitely don't need this.
    def walkFeatures(geojson, operate):
        features = geojson if isinstance(geojson, PyArray) else geojson['features'] if isinstance(geojson, PyJsonObjectAdapter) and isinstance(geojson['features'], PyArray) else None
        if features:
            for idx in range(len(features)):
                feature = features[idx]
                if isinstance(feature, PyJsonObjectAdapter):
                    if feature.has_key('features'):
                        walkFeatures(feature['features'], operate)
                    elif feature.has_key('geometry') or feature.has_key('geometries') or feature.has_key('coordinates'):
                        operate(feature)
                        
        elif isinstance(geojson, PyJsonObjectAdapter):
            if feature.has_key('geometry') or feature.has_key('geometries') or feature.has_key('coordinates'): 
                operate(geojson)
    
    # Operates on a feature, in this example, all of the features of this layer are expected to be Point objects, which we configure as Leaflet markers          
    def operateOnFeature(feature):
        if isinstance(feature, PyJsonObjectAdapter):
            
            marker = {
                'render': 'icon',
                'icon': {
                    'path': 'material/location_on',
                    'size': {
                        'width': 36,
                        'height': 36
                    },
                    'color': '#9bfa03'
                }
            }

            if feature.has_key('properties'):
                properties = feature['properties']
                LOCATION_TYPE = properties.get('LOCATION_TYPE')
                LOCATION_NAME = properties.get('LOCATION_NAME')
                ADDRESS_LINE1 = properties.get('ADDRESS_LINE1')
                if LOCATION_TYPE == 'Headquarters':
                    marker['icon']['color'] = '#ff0000'
                marker['tooltip'] = {
                    'content':  {
                        'text':	LOCATION_NAME
                    }
                }
                marker['popup'] = {
                    'content':  {
                        'text': properties
                    }
                }
            feature['marker'] = marker
    walkFeatures(value, operateOnFeature)
    return value
```

Polygon, Polyline, Rectangle, and Circle layers

  • The default schema of vector Polygon and Polyline objects has been altered to allow specifying a name and properties property which is supplied as a parameter to click event handlers. Previous schema shapes are still accepted. For example,

    Old default schema shape:

    polyline: [ // An array of polyline layers
        { // A single layer of polylines
            polylines: [ // Polylines belonging to this single layer
                [ // A polyline, defined by an array of points
                    { // A single point of this polyline, requires min of two points
                        lat: null,
                        lng: null
                    }
                ]
            ]
        }
    ]
    

    New default schema shape:

    polyline: [ // An array of polyline layers
        { // A single layer of polylines
            polylines: [ // Polylines belonging to this single layer
                {
                    name: "", // The name of this polyline
                    properties: {
                        // Properties of this polyline
                    },
                    points: [ // An array of points
                        { // A single point of this polyline, requires min of two points
                            lat: null,
                            lng: null
                        }
                    ]
                }
            ]
        }
    ]
    
  • name and properties properties where added to circle and rectangle vector schemas

  • The name property which existed at the layer level of vector objects has been hidden from the schema, in exchange for individual object level specifying of name (see above). To prevent regression, the name that was defined on the layer will be used if the name defined on the vector object is empty or undefined.

To create a basic GeoJSON layer, you can use this mapping tool.
For real world example datasets, see data.gov. Beware, these datasets can be quite large.

Other changes

  • We now supply the true lat, lng coordinates of an element when a vector or marker is clicked. The true coordinates are derived directly from click event data, instead of being initialized from configuration. The change is a result of realizing that it is possible to make a marker draggable through configuration, and as such initializing coordinates from configuration would present incorrect data.

  • Configuration of objects is now more flexible. Any additional configuration is merged and forwarded directly to the Leaflet API. This is an advanced feature, so use with care. Its purpose is to prevent having to submit a request for a property listed in the API, on say, a marker object, and block a designer’s work until that feature is finally added. For example: on a marker object you should be able to make a marker draggable by simply adding the draggable property and setting it to true, as outlined in the Leaflet documentation.

  • Changes to the configuration of a GeoJSON layer will result in that layer being redrawn. We detect changes by performing a deep equality comparison on current layers and previous layers. An approach mostly unique to GeoJSON layers only. Leaflet is a low level JS library, and as such requires some arm twisting to work nicely with React.

3 Likes