Skip to content

Releases: GEOLYTIX/xyz

v3.3.0 Update (06/23/2020)

23 Jun 08:26
Compare
Choose a tag to compare

Release Update v3.3.0 - June 23rd 2020

Enhancement 🚀

Templates

  • Templates loaded from src will override any literal definition as the template value 280

Viewport

  • Replace ST_DWithin with ST_Intersects in viewport constructions

InfoJ

  • Initial support for infoj entry queries 272

Bundles ♻️

  • Remove Openlayers from XYZ bundle. 292

µhtml

  • Add µhtml support. 281
import {render, html, svg} from 'uhtml';

render(document.body, html`<h1>Hello 👋 µhtml</h1>`);

Gazetteer 📌

  • Support for "space_wildcard": true and "minLength" for searched dataset.
  • Results returned in promises array.

Images

  • Images arranged in a flex box.

Bug Fixes 🐛

  • Fix for intersection checks on geometry drawing
  • Fix for draw interaction methods not being referenced correctly
  • Fix 'x-forward-for' address in email notifications. 293

v3.2.0 Update (06/05/2020)

05 Jun 08:24
Compare
Choose a tag to compare

Release Update v3.2.0 - June 5th 2020

Enhancement 🚀

11ty 🎓

  • 11ty the simple static site generator has now been implemented in the framework.
  • Documentation has now been initialized.

Layers

  • Grouped layers - now can be displayed all at once.

API

  • Query API now accepts statement_timeout param.

Dataviews

  • Lib Dataviews create to accept plugins for chartjs.

Location

  • Added removeCallback function

Views 👓

  • Blog view template has been created.

Geometry

  • Geometry entry - now supports query property.

Injoj

  • Entries now use title property. To use toolip set tooltip, checkboxes retain "name" property. Aligns with dataviews and columns setup.
  • This is a disruptive change - requires update to all infoj configurations.

Vercel Config :shipit:

  • now.json to include all public files not just views.

Gazetteer

  • Gazetteer endpoint - results now returned from multiple datasets. Previously - more datasets searched when no results found.
  • Gazetteer searchbox - entries have now source label.
  • Gazetteer - optional dataset label. To display source dataset for results add "label": true in gazetteer params.

Isolines

  • Settings for isolines - now wrapped in expandable group-style container.
  • Isolines - restored support for "meta" entry - so that custom isolines have keep their metadata.

ChartJS 📊

  • Labels for ChartJS can now be a function taking the query response as input.

Packages 📦

  • Make moment external for webpack bundle.
  • Upgrade to Webpack5 + Terser Plugin. ECMA:8 required as option for Openlayers module.

Bug Fixes 🐛

  • Isoline settings for locations - fixed layout broken by display: flex of the container.
  • Fix for cluster aggregate param.
  • Fix for isolines draw interaction. Features to be drawn with interaction.draw.feature.

v3.1.1 Update (05/22/2020)

02 Jun 08:44
Compare
Choose a tag to compare

Release Update v3.1.1 - May 22nd 2020

Enhancement 🚀

Token

  • Token has been removed from all lib request calls. All calls should work with cookies alone.

Legend

  • Containers with geometry collection legend to get display: block; otherwise legend displayed inline as flex element.

Error Messages

  • Return error message if unable to connect to DBS in query.

CDN

  • Add raw.githubusercontent.com and gitcdn to csp image source.

Bug Fixes 🐛

  • Fix for zero env launch. getWorkspace to get default if no env provided
  • Fix to geometry checkbox - previously hiding a geometry would also hide geometry collection defined on the same location.
  • Fix for roles. #273
  • Assign tab to dataviews in array.

v3.1.0 Update (05/18/2020)

18 May 08:18
Compare
Choose a tag to compare

Release Update v3.1.0 - May 15th 2020

Enhancement 🚀

Dataviews

  • Check whether remove dataview function exists before trying to remove dataview. A dataview will be pushed into the location array but may not be created if the display is false. This caused an error when trying to remove location with dataviews.
  • Dataviews have an active flag to control whether they should be updated.
  • Dataviews may have a center property to send the lat lng of the current map view centre to the Query API.
  • Dataviews now support a queryparams object to be sent to the Query API.
  • Tabulator grouped columns - removed displaced borders for cleaner look. #249

Gazetteer

  • Datasource Gazetteer may now be set without association to a layer.
  • Add gazetteer.callback prior to select for glx source.
{
  "source": "lad",
  "table": "coop.vw_uk_glx_geodata_admin_lad_new",
  "label": "lad_name",
  "geom": "geom_4326",
  "dbs": "XYZ"
}

Hooks

  • Failing to select location will now remove the hook and not prompt the error message twice.
  • The location.remove() will now be assigned before async call to Location API. This ensures that a location can be removed before the location decorator in the callback of the location/get request. #267

Attribution

  • Check on attribution layer to prevent error thrown when mapview is used without attriubution target.

Timeout Added

  • STATEMENT_TIMEOUT maybe set as environment setting. This will override the default statement of "10000" (10 seconds) for all dbs requests.

Tap

  • Touch interactions set a timeout of 1 second to prevent quick multi-tap for highlight / selection.

Clusters

  • Markers - markerMin/Max now supported within theme markers. #251

Database

  • Integer and numeric fields now can be set to empty - previously resulted in database column type error.

API

  • Query API to support parameter array or stringified body as $1.
  • Shortcircuit 202 select requests.

Bug Fixes 🐛

  • Icon in theme drop down no longer removed after switch.
  • Documents - newly uploaded document added at the end of the list, original file name is retained instead of unix timestamp. #262
  • Export JSON and CSV from dataview table fixed.

v2.1.1 Update (29/01/2020)

29 Jan 13:21
Compare
Choose a tag to compare

Release Update v2.1.1 - January 29 2020

Enhancement 🚀

Interaction/edit - hover class on each menu row. (489ff92)

    const menu = _xyz.utils.wire()`<ul>`;
- _xyz.mapview.interaction.edit.feature.length && menu.appendChild(_xyz.utils.wire()`<li onclick=${update}>Update</li>`);
+   _xyz.mapview.interaction.edit.feature.length && menu.appendChild(_xyz.utils.wire()`<li class="off-white-hover" onclick=${update}>Update</li>`);

-  _xyz.mapview.interaction.edit.feature.length && menu.appendChild(_xyz.utils.wire()`<li onclick=${undo}>Undo</li>`);
+   _xyz.mapview.interaction.edit.feature.length && menu.appendChild(_xyz.utils.wire()`<li class="off-white-hover" onclick=${undo}>Undo</li>`);

Info tip - (d2c5044)

  • HTML content render correction
    if (this.node) this.node.remove();

-   this.node = _xyz.utils.wire()`<div class="infotip">${info}`;
+  this.node = _xyz.utils.wire()`<div class="infotip">`;
+
+  this.node.innerHTML = info;

    _xyz.mapview.node.appendChild(this.node);

New Logout Button (ec7a0c1) Logout Button

Added 'webkit' prefixes for background-size (b8c110a)

+ -webkit-background-size: contain;

OpenCage Gazetteer (6a049a2)

  • Added environment variable 'KEY_OPENCAGE'.
"gazetteer": {
                "provider": "OPENCAGE",
                "placeholder": "e.g. Venice",
                "bounds": "-13,48,5,62",
                "code": "GB"
            }

mod/gazetteer/opencage.js

const env = require('../env');
const fetch = require('../fetch');

module.exports = async (term, gazetteer) => {

	const url = `https://api.opencagedata.com/geocode/v1/json?q=${encodeURIComponent(term)}` 
	+ `${gazetteer.code ? `&countrycode=${gazetteer.code}` : ''}`
	+ `${gazetteer.bounds ? '&bounds=' + decodeURIComponent(gazetteer.bounds) : ''}`
	+ `&key=${env.keys.OPENCAGE}`;

	const fetched = await fetch(url);

	if (fetched._err) return fetched;

	return await fetched.results.map(f => ({
		id: f.annotations.geohash,
		label: f.formatted,
		marker: [f.geometry.lng, f.geometry.lat],
		source: 'opencage'
  }));
}

Styling - added hide all for legends in categorized themes (f508ebf)

public/js/layers/view/style/_styles.mjs

// Allow hide all from legend
    panel.appendChild(_xyz.utils.wire()`
      <div class="switch-all" style="font-size: 90%; display:none;">Click on labels to switch visibity or 
      <a class="primary-colour" style="cursor: pointer;"
      onclick=${e => {
        e.stopPropagation();

        layer.style.theme.hideAll = layer.style.theme.hideAll ? false : true; // control flag

        if(!layer.filter.legend[layer.style.theme.field]) layer.filter.legend[layer.style.theme.field] = {};

        layer.filter.legend[layer.style.theme.field].ni = []; // set initial values for filters
        layer.filter.legend[layer.style.theme.field].in = [];

        if(layer.style.theme.hideAll) { // apply all exclusions

          Object.keys(layer.style.theme.cat).map(c => layer.filter.legend[layer.style.theme.field].ni.push(c));
          layer.filter.legend[layer.style.theme.field].in = Object.keys(layer.style.theme.cat);
            
        }
        // count nodes to update excluding 'Multiple locations on cluster layers
        let childNodes = layer.format === 'cluster' ? e.target.parentElement.nextSibling.children.length - 2 : e.target.parentElement.nextSibling.children.length;

        for(let i = 0; i < childNodes; i++){ // apply styling
          e.target.parentElement.nextSibling.children[i].style.textDecoration = layer.style.theme.hideAll ? 'line-through' : 'none';
          e.target.parentElement.nextSibling.children[i].style.opacity = layer.style.theme.hideAll ? 0.8 : 1;
          e.target.parentElement.nextSibling.children[i].style.fillOpacity = layer.style.theme.hideAll ? 0.8 : 1;
        }

        layer.reload(); // reload layer

    }}>switch all</a>.`);

public/js/layers/view/style/_styles.mjs

 function applyTheme(layer) {
+      // enable or hide 'switch all' filter.
+      panel.querySelector('.switch-all').style.display = layer.style.theme && layer.style.theme.type === 'categorized' ? 'block' : 'none';
  
      // Empty legend.
      layer.style.legend && layer.style.legend.remove();

Location/List ⚠️

  • Added check on 'where' parameter (dd95d6a)

/routes/api/location/list.js

let q = `
        SELECT ${fields.join(',')} 
        FROM ${tableDef.from}
+      ${tableDef.where ? `WHERE ${tableDef.where}` : ``}
        ORDER BY ${tableDef.orderby || ''} NULLS LAST
        LIMIT ${tableDef.limit || 100};`;
  • Added check on 'orderby' parameter (97c192d)

/routes/api/location/list.js

let q = `
        SELECT ${fields.join(',')} 
        FROM ${tableDef.from}
        ${tableDef.where ? `WHERE ${tableDef.where}` : ``}
+      ${tableDef.orderby ? `ORDER BY ${tableDef.orderby}` : ``} NULLS LAST
        LIMIT ${tableDef.limit || 100};`;

Legend for categorized clusters (f517441)

  • removed svg container

public/js/layers/view/style/legend/clusterCategorized.mjs

    let image_container = _xyz.utils.wire()`<div style="height: 24px; width: 24px;">`;

-    let svg = _xyz.utils.wire()`<svg>`;
+   let image = _xyz.utils.wire()`<img height=20 width=20>`;

-    let image = _xyz.utils.wire(null, 'svg')`
-    <image x=0 y=0 width=20 height=20>`;
+   image.setAttribute('src', _xyz.utils.svg_symbols(Object.assign({}, layer.style.marker, cat[1].style || cat[1])));

-    image.setAttribute('href', _xyz.utils.svg_symbols(Object.assign({}, layer.style.marker, cat[1].style || cat[1])));
-
-    svg.appendChild(image);
-
-    image_container.appendChild(svg);
+   image_container.appendChild(image);

    legend.appendChild(image_container);

public/js/layers/view/style/legend/clusterCategorized.mjs

  let imageMulti_container = _xyz.utils.wire()`<div style="height: 40px; width: 40px;">`;

-  let svgMulti = _xyz.utils.wire()`<svg>`;
-
-  imageMulti_container.appendChild(svgMulti);
-
-  let imageMulti = _xyz.utils.wire(null, 'svg')`
-  <image x=0 width=40 height=40 />`;
+  let imageMulti = _xyz.utils.wire()`<img height=40 width=40>`;

-  imageMulti.setAttribute('href', _xyz.utils.svg_symbols(layer.style.markerMulti));
+ imageMulti.setAttribute('src', _xyz.utils.svg_symbols(layer.style.markerMulti));

-  svgMulti.appendChild(imageMulti);
+ imageMulti_container.appendChild(imageMulti);

  legend.appendChild(imageMulti_container);

Legend for cluster graduated theme (d7b38a3)

  • removed svg container

public/js/layers/view/style/legend/clusterGraduated.mjs

    let image_container = _xyz.utils.wire()`<div style="height: 24px; width: 24px;">`;

-    let svg = _xyz.utils.wire()`<svg>`;
+   let image = _xyz.utils.wire()`<img width=20 height=20>`;

-    let image = _xyz.utils.wire(null, 'svg')`
-    <image
-      x=0 y=0
-      width=20
-      height=20>`;
+    image.setAttribute('src', _xyz.utils.svg_symbols(Object.assign({}, layer.style.marker, cat.style)));

-    image.setAttribute('href', _xyz.utils.svg_symbols(Object.assign({}, layer.style.marker, cat.style)));
-
-    svg.appendChild(image);
-
-    image_container.appendChild(svg);
+   image_container.appendChild(image);

    legend.appendChild(image_container);

Geometry entry (d2f40fb)

  • Now drawn underneath source layer
    function drawGeom() {
+
      entry.geometry = entry.value && _xyz.mapview.geoJSON({
        geometry: JSON.parse(entry.value),
        dataProjection: '4326',
+      zIndex: _xyz.layers.list[entry.location.layer.key].L.getZIndex() - 1,
        style: new _xyz.mapview.lib.style.Style({
          stroke: entry.style.strokeColor && new _xyz.mapview.lib.style.Stroke({
            color: _xyz.utils.Chroma(entry.style.color || entry.style.strokeColor).alpha(1),

Added in 'Bring Later to front (b0b3089)

  • The button will only be enabled on layers that are active

image

public/js/layers/bringToFront.mjs

export default _xyz => function () {

	const layer = this;

	if(layer.L.getZIndex() === 1000) return;

	Object.values(_xyz.layers.list).map(_layer => {

		if( _layer.format !== 'tiles') _layer.L.setZIndex(_layer.style.zIndex || 1) && (_layer.display && _layer.reload());

	});

	layer.L.setZIndex(1000);

	layer.reload();

}

Cluster endpoint ⚠️

  • label included in all queries (7663dfc)
  • Removed hard coded label (bed0222)

routes/api/layer/cluster.js

var q = `
        ${with_sql || ''}
        SELECT
          count(1) count,
          SUM(size) size,
-         label,
+        ${label && label !== 'count' ? 'label,' : ''}
          ${cat_sql || ''}
          ${xy_sql}
        FROM ${agg_sql}`;

Sql text filter (9306a2c)

  • Added regular expression for single quote
    if((filter.like)) {
      const likes = decodeURIComponent(filter.like).split(',')
        .filter(like => like.length > 0)
-  ...
Read more

v2.1.0 Update (13/01/2020)

13 Jan 16:53
Compare
Choose a tag to compare

Release Update v2.1.0 - January 13 2020

Enhancement

- Template Parameter now used in reports (e1d88e7)

routes/report.js

async function view(req, res, token = { access: 'public' }) {

  let tmpl;

  if (req.query.template.toLowerCase().includes('api.github')) {

    const response = await fetch(
      req.query.template,
      { headers: new fetch.Headers({ Authorization: `Basic ${Buffer.from(env.keys.GITHUB).toString('base64')}` }) });

    const b64 = await response.json();
    const buff = await Buffer.from(b64.content, 'base64');
    tmpl = await buff.toString('utf8');

  } else {

    const response = await fetch(`${req.headers.host.includes('localhost') && 'http' || 'https'}://${req.headers.host}${env.path}${req.query.template}`);
    if (response.status !== 200) return res.type('text/plain').send('Failed to retrieve report template');
    tmpl = await response.text();

  }

  const html = template(tmpl, {
    dir: env.path,
    host: `${req.headers.host.includes('localhost') && 'http' || 'https'}://${req.req.hostname}${env.path || ''}`,
    token: req.query.token || token.signed || '""'
  });

  //Build the template with jsrender and send to client.
  res.type('text/html').send(html);

};

- Changed 'CDN' to 'Github' in workspace (a79170c)

mod/workspace/init.js

// Load workspace from github.
    if (env.workspace_connection.split(':')[0] === 'github') {

- Charts - Responsive Flag default now set to true (1af231a)

responsive: entry.chart.responsive === undefined ? true : false

- Gazetteer callback and Scroll restoration (Mobile) (f20284a)

public/js/gazetteer.mjs

 xhr.onload = e => {
      
      // Send results to createFeature
      if (e.target.status === 200) gazetteer.createFeature(e.target.response);

      if (gazetteer.callback) return gazetteer.callback(e.target.response);
    
      record.callback && record.callback(e.target.response);
    };

public/views/mobile.js

if ('scrollRestoration' in history) history.scrollRestoration = 'auto';

- Location View

  • Check for Edit.delete (d123dc1)
    -- Fix to delete icon enabled by default

public/js/locations/view/_view.mjs

location.layer.edit && location.layer.edit.delete && header.appendChild(_xyz.utils.wire()`
  • Added check for geometry editing (092cf16)
    -- Set "edit": {"geometry": true} on layer in order to enable geometry editing but not allow new features to be created.

public/js/locations/view/_view.mjs

 location.layer.edit && location.layer.edit.geometry && header.appendChild(_xyz.utils.wire()`

Removal of pointonsurface from infoj handler (c8e48ea)

routes/api/location/select/infoj_values.js

REMOVAL

    fields.push(`\n   ARRAY[ST_X(ST_PointOnSurface(${params.layer.geom})), ST_Y(ST_PointOnSurface(${params.layer.geom}))] AS PointOnSurface`);

Dashboard - Check for dashboard Target (e30ea80)

  • Previously checked for referred item target

public/js/dataview

 if(val.type === 'pgFunction' && val.dashboard && entry.title === val.dashboard) {

        _xyz.dataview.pgFunction({
          entry: val, 
          container: document.getElementById(entry.target_id) || flex_container
        });
      
      }

Ordered List - Fields (From, where, orderby, limit) (e8e22a8)

  • orderedlist is now defined with FROM, WHERE, ORDERBY, LIMIT fields. In order to be more in line with sql statements.
"type": "orderedList",
"title": "Nearest competitors",
"display": true,
"class": "_report",
"target_id": "xyz_table5",
"layout": "fitColumns",
"from": "dev.uk_glx_sites a, geodata.uk_glx_open_retail_points b",
"where": "a.id = $1",
"orderby": "ST_Transform(a.geom_4326, 3857) <#> ST_Transform(b.geom_p_4326, 3857)",
"limit": 5,

Bug Fixes

- Fix to Location Table (40eaca4)

  • Removed reference when processing aggregate table.columns
// get table columns
    const _columns = Object.values(table.columns).map(col => { return Object.assign({}, col) });

    // get table aggregate columns if defined
    const _agg_columns = Object.keys(table.agg || {}).map(key => {
      return Object.assign({}, {field: key}, table.agg[key]);
    });

    table.update();

    // group columns if grouped defined
    let columns = _xyz.dataview.groupColumns({columns: _columns.concat(_agg_columns)});

Fix for fallback fill/stroke opacity

  • Previously would fallback to 1 making it impossible to set 0.
    -- MVT (427a1e5)
    -- Geojson (fe52037)
color: _xyz.utils.Chroma(style.strokeColor).alpha(style.strokeOpacity === undefined ? 1 : parseFloat(style.strokeOpacity) || 0).rgba(),
color: _xyz.utils.Chroma(style.fillColor).alpha(style.fillOpacity === undefined ? 1 : parseFloat(style.fillOpacity) || 0).rgba()

-- Geometry (7bb46eb)

public/js/locations/view/geometry/_geometry.mjs

color: _xyz.utils.Chroma(entry.style.fillColor || entry.style.strokeColor).alpha(entry.style.fillOpacity === undefined ? 1 : parseFloat(entry.style.fillOpacity) || 0).rgba()

-- Location draw (a7f3224)

public/js/locations/draw.mjs

color: _xyz.utils.Chroma(location.style.fillColor).alpha(location.style.fillOpacity === undefined ? 1 : (parseFloat(location.style.fillOpacity)) || 0).rgba()

-- Polystyle (1cbf137)

public/js/layers/view/style/polystyle.mjs

value=${style.fillOpacity === undefined ? 1 : (parseFloat(style.fillOpacity) || 0)}

Fix for Filter Delimiter (ac3c584) (https://github.com/GEOLYTIX/xyz/issues/230)

  • trim() function added

mod/pg/sql_filter.js

if((filter.like)) {
      const likes = decodeURIComponent(filter.like).split(',')
        .filter(like => like.length > 0)
        .map(like => `${field}::text ILIKE '${like.trim()}%'`);
      sql_filter.push(likes.join(' OR '));
      sql_filter.push(conjunction);
    }

Fix for NaN or Null values (85fc7f2) (https://github.com/GEOLYTIX/xyz/issues/231)

public/js/locations/view/infoj.mjs

if (!entry.edit && entry.value === null) continue

Fix for Graph/Toggle Issue (https://github.com/GEOLYTIX/xyz/issues/232) (218aa9f)

Fix for MVT's - Now Supports 0 as a theme category value (e6e38b7) (https://github.com/GEOLYTIX/xyz/issues/227)

public/js/mapview/mvt.mjs

if (theme && theme.type === 'categorized') {

        const field = feature.get(theme.field);

        if(field === undefined) return Object.assign(style, {});
  
        Object.assign(
          style,
          (theme.cat[field] && theme.cat[field].style) || theme.cat[field]);
      }

Dependency Changes

Added Flatpickr (aae6ea1)

"flatpickr": "^4.6.3",

public/js/locations/view/edit/date.mjs

export default _xyz => entry => {

  const _input = _xyz.utils.wire()`<input type="text" placeholder="Pick from calendar.">${entry.type === 'datetime' && _xyz.utils.formatDateTime(entry.value) || _xyz.utils.formatDate(entry.value) || ''}`;

  const input = _input.childNodes[0];

  //input.value = entry.type === 'datetime' && _xyz.utils.formatDateTime(entry.value) || _xyz.utils.formatDate(entry.value) || '';

  entry.val.appendChild(input);

  _xyz.utils.flatpickr({
    element: input,
    callback: dateStr => {

      input.value = dateStr;
      
      const date_unix = _xyz.utils.meltDateStr(dateStr);

      entry.location.view.dispatchEvent(
        new CustomEvent('valChange', {detail:{
          input: input,
          entry: entry,
          newValue: date_unix
        }}));
    }
  });

};

v2.0.1 Update (02/12/2019)

08 Jan 12:35
Compare
Choose a tag to compare

Release Update v2.0.1 - December 12 2019

New Features

Stroke Opacity - strokeOpacity now supported

Note: Strokes are drawn on top of fill. Fill and stroke opacity at 0.4/0.2 will mean that the stroke opacity is 0.6 not but 0.2.

image

Style panel hidden with theme/themes will still show the legend of the theme but no dropdown to change theme.

image

Custom Scripts now supported (https://github.com/GEOLYTIX/xyz/issues/209)

Custom scripts are used to assign functions to the window object. Whoever writes the script must be aware of the risk of non unique names and overwritting existing window methods. They will also need to be aware of inputs into the script.

window._xyz_csv_import = (_xyz, layer) => {
    
  layer.view.appendChild(_xyz.utils.wire()`
  <button
    class="btn-wide primary-colour"
    onclick=${e => {
      e.stopPropagation();
      alert('bar')
    }}>Foo`);
      
}

The scripts utility method returns a promise for loading scripts via a script tag added to the document header.


export function loadScript(src) {

  return new Promise((resolve, reject) => {
    const script = Object.assign(
      document.createElement("script"), {
      src: src,
      onerror: () => reject(console.error('failed:'+src)),
      onload: () => resolve(console.log('loaded:'+src))
    });
  
    document.head.appendChild(script);
  });

}

Script sources can be defined as an array in the locale root. The scripts can be loaded from private repositories via the cdn proxy endpoint.

"scripts": [
  "/dir/proxy/cdn?uri=https://api.github.com/repos/GEOLYTIX/xyz_resources/contents/client/run_model.js&source=GITHUB&type=application/javascript",
  "/dir/proxy/cdn?uri=https://api.github.com/repos/GEOLYTIX/xyz_resources/contents/client/csv_import.js&source=GITHUB&type=application/javascript"
],

Scripts are asynchronously loaded prior to layers being loaded. Layers will only load after all script load promises resolve.


function loadLocale(callback) {

    // Assign key from params of first in locales list.
    const locale = (_xyz.locale && _xyz.workspace.locales[_xyz.locale])
      || (_xyz.hooks && _xyz.hooks.current.locale)
      || Object.keys(_xyz.workspace.locales)[0];

    // Assigne workspace locales from locales list and input params.
    _xyz.workspace.locale = Object.assign({ key: locale }, _xyz.workspace.locales[locale]);

    loadScripts(callback)
  };

  function loadScripts(callback){

    if (!_xyz.workspace.locale.scripts) return loadLayers(callback);

    const scripts = _xyz.workspace.locale.scripts.map(script=>_xyz.utils.loadScript(script));

    Promise.all(scripts).then(()=>{
      loadLayers(callback)
    });
  }

  function loadLayers(callback){

    Object.keys(_xyz.workspace.locale.layers)
    .filter(key => key.indexOf('__') === -1)
    .forEach(key => {
      _xyz.layers.list[key] = _xyz.layers.decorate(_xyz.workspace.locale.layers[key]);
    });

    callback && callback(_xyz);
  }

Scripts [array] can defined by their name in the layer root.

"scripts": [
  "_xyz_csv_import"
],

Or as a single script can be defined by it's name in a location entry in the infoj.

{
  "script": "_xyz_run_model"
}

Enhancement

Hide Layer Panel Style - Style panel will not be added to the layer view if hidden: true (https://github.com/GEOLYTIX/xyz/issues/204)

"style": {
    "hidden": true,
    "marker": {
        "type": "target",
        "fillColor": "#D8D8F6",
        "layers": {
            "0.6": "#A37871"
        }
    },
    "markerMulti": {
        "type": "target",
        "fillColor": "#D8D8F6",
        "layers": {
            "0.6": "#A37871"
        }
    }
}

image

Sub queries - It is possible to put subqueries into fieldfx by bracketing the query.(https://github.com/GEOLYTIX/xyz/issues/198)

 {
   "group": "Population within 5min",
  "label": "2012",
  "field": "_pop__12_5min",
  "fieldfx": "SUM(b.pop__12)",
  "level": 1,
  "type": "integer",
  "lookup": {
    "table_a": "mb.house_list",
    "table_b": "geodata.uk_glx_geodata_hex_1k",
    "geom_a": "isoline_5min",
    "geom_b": "geom_p_4326"
  }
},
  "group": "Population within 5min",
  "label": "2011",
  "field": "_pop__11_5min",
  "fieldfx": "(SELECT SUM(B.pop__11) FROM mb.house_list A, geodata.uk_glx_geodata_hex_1k B WHERE A.buncode = $1 AND ST_INTERSECTS(A.isoline_5min, B.geom_p_4326))",
  "type": "integer"
},

Transport of infoj from middleware to client - Values are now transported from the middleware to the client.

const infoj = location.layer.infoj.map(entry => {
      entry.value = e.target.response[entry.field]
      return entry;
    })

Display geometry values - Geometry values will only be displayed by default if the display is set to true in the infoj entry. (https://github.com/GEOLYTIX/xyz/issues/208)

Icon Change

image

these icons like an anchor of [0.5, 0.95]
which puts the anchor low but not on the edge.
also markerMin, markerMax only have an effect inside the style object.
also fixed an issue where the hover does not work on the first cluster location. index needs to start at 1 not 0 for cluster.
svg can be scaled like so:

image

meaning it is possible to have all things at a reasonable size and not the general cluster way to big and the svg way too small.
object assign means that the svg has to be set to null if a different type of target is to be used for the highlight style. for example:
image

Stored queries (https://github.com/GEOLYTIX/xyz/issues/220)

Queries to a connected data source must never be sent from the client to the middleware. All queries must already be available in the workspace/locale and only reference to the stored query should be provided by the client.

Custom Query Timeout (#221)

We have now added the possibility to override the query timeout of 10 seconds on the node postgres pool used to communicate between the middleware and database cluster.

Bug Fixes

  • Filter in - Allows for special characters as values. These values are encoded and decoded.
  • Empty Groups - No longer render if there is no data.
    image

v1.6.2 Update (30/08/2019)

02 Sep 07:42
Compare
Choose a tag to compare

Release Update v1.6.2 - August 30 2019

Changes

  • Charts (Min/Max set on Chart Container, Fallback height)
  • Updated CSS for Context Menu
  • Ordered List (Addition of Lookup Clause)
  • Reports (Support for 'Hidden from report')
  • Cluster Layer (Added ClusterArea Object)(Get properties for underlying polygon)
  • Protocol Schema Check (AWS)
  • Reports (AWS Compatibility)
  • pgFunction Type
  • Table View (Lateral Query)
  • Point Edit (Context Menu will no longer appear in editing mode)
  • Editing Panel (Name change to 'Add new features')
  • Table Definition (support for pgFunction, headerSort disabled by default)
  • Tablulator Tables (Better support for table formatters)
  • Ordered List (headerSort disabled by default)
  • Login Messages (Fixed Login Messages)
  • Popup (Optional closeButton Parameter)

Bug Fixes

  • IsoLine (Metadata)
  • Orderded List (Fix for non prexisiting table container)
  • Tableview (Assign Button) (Redraws on Current Tab)
  • Gazetter (Fix Select)
  • Table Checkboxes (CSS)
  • Fix for ACL on Windows 10
  • Fixx for Wrapped Checkbox names (CSS)
  • Custom Dropdown (singleSelect)

v1.6.1 Update (01/07/2019)

10 Jul 09:25
Compare
Choose a tag to compare

Release Update v1.6.1 - July 01 2019

Changes

  • MVT Editing

    • Measurement functions for polygons
    • Support for Line editing
    • Ability to move points
  • Toggle hover for labels

  • Hyper Drop Downs

  • Additional Geometries

    • Display flag for none editable catchments
  • Mapbox API Logs (Support)

  • HERE API Logs (Support)

Bug Fixes

  • Labels
    • Exit when none defined
  • Table Views
    • CSS fix

v1.5.0 Update (04/29/2019)

29 Apr 15:45
Compare
Choose a tag to compare

Release Update v1.5.0 - April 29 2019

Fixes

Gazetteer search for numbers only

  • A valid lat long position will now cancel the search to backend / 3rd party service.

Changes

Workspaces

The workspace editor is now nested in the desktop. Workspace methods are in the XYZ control library. The json editor library has been removed from the repo. The code mirror library has been added and should be used to edit json.

Workspace routes are:

/workspace/get

Get the workspace from XYZ host.

/workspace/set

Post a workspace to the XYZ host. Will run checks, load workspace into memory and store the workspace as a new record in the workspace table.

The workspace is stored in global.workspace.current

Views

Scripts which only apply to a single view are stored with the html template in the public /views folder. Styles which only apply to a single view are in the head of that view. Only shared SCSS stylesheets are in the public /css folder.

Scripts sources should not be defined after the body. All sources should be defined in the head and user the defer / async declaration in order to prevent the page locking while loading the script.

/register

Small print has been added to the register view. The submit button should only be active if the checkbox in regard to the small print is checked.

Google recaptcha has been added to Login and Register forms. This is enabled if a captcha key and secret are provided in the environment key GOOGLE_CAPTCHA.

A mask has been added to the desktop view. This mask shades the view to prevent user input. e.g. While waiting for a response to workspace checks / load.

Log rocket

Log rocket has been added to the repo. A confirmation dialog will ask whether a session should be recorded. The log rocket button will be visible if a log rocket key is provided in the environment key LOG_ROCKET.

Authentication

Token authentication is now happening pre route validation.

Token

The /token endpoint will return an API key and store the key in the ACL database in the api field for the user. Token do not timeout but can never be used to login or for administrative tasks. An API token will always be checked against the database to create a signed token with the user roles.

Validation

All params must be provided on the querystring. The querystring params must be validated in route schema.

Additional checks are run in an array of pre-handler methods.

User routes

Routes for the management of users are:

/user/admin The admin view for the ACL.

/user/approve The endroute to approve a user with an approval token.

/user/verify The endroute to approve a user with an verification token.

/user/log Returns the access log for a user.

/user/list Returns a list of all users.

/user/update Updates a user field in the ACL.

/user/delete Endpoint to delete a user from the ACL.

/user/block Endpoint to block a user.

User schema

create table users
(
	"_id" serial not null,
	email text not null,
	password text not null,
	verified boolean default false,
	approved boolean default false,
	verificationtoken text,
	approvaltoken text,
	failedattempts integer default 0,
	password_reset text,
	api text,
	approved_by text,
	access_log text[] default '{}'::text[],
	blocked boolean default false,
	roles text[] default '{}'::text[],
	admin_workspace boolean default false,
	admin_user boolean default false
);

Roles

Roles are set on the layer.

"roles": {
  "boo" : null,
  "foo" : {
    "retailer": {
      "in": [
        "Tesco",
        "Sainsburys"
      ]
    }
  }
}

If a roles are set means that the layer is not available to anybody who doesn't have a role from the layer. If a filter is set on the role means that the user has access to the layer but the filter will be applied to any query.

Error Codes

The 406 error code has been retired in favour of the more common 400 error code. Error code should return a new Error('msg').

Swagger

The fastify swagger plugin has been added to respond with a json to describe the API on the /swagger/json route.

Checkbox

  • Boolean data type is displayed as a checkbox element in the location view and can be also set up as editable. Boolean data type is stored in the database as boolean.

  • "type": "boolean"

  • Checkbox will show when they are not editable.

image

  • Boolean module has been moved from /edit to /view for locations.
  • The method will return with the input disabled if not editable otherwise the onchange event will be attached to the checkbox.
  • Check box filtering enabled.