0

We use a javascript Map object to store computational expensive style functions which are called in order to style features in an Openlayers map render.

The style function requires a nested JSON object which is retrieved from each feature to be styled.

Map.has() will not recognise the JSON object due to the proto parameter. We can use stringify to create a 'Hashmap' key which can be evaluated in subsequent calls of the style function.

Our understanding is that JSON stringify is computational expensive and we would like to ask whether there are known alternatives to achieve the same as shown in this code but without the use of JSON stringify.

  let memoizedStyles = new Map()

  new ol.layer.VectorTile({
    source: new ol.source.VectorTile(source),
    style: feature => {

      const style = feature.get('style')

      const styleStr = JSON.stringify(style)

      if (memoizedStyles.has(styleStr)) return memoizedStyles.get(styleStr)
  
      const olStyle = new ol.style.Style({
        stroke: style.strokeColor && new ol.style.Stroke({
          color: style.strokeColor,
          width: parseFloat(style.strokeWidth) || 1
        }),
        fill: style.fillColor && new ol.style.Fill({
          color: style.fillColor
        })
      })
  
      memoizedStyles.set(styleStr, olStyle)
  
      return olStyle
    }
  })
Dennis Bauszus
  • 1,624
  • 2
  • 19
  • 44
  • 1
    "*Our understanding is that JSON stringify is computational expensive*" - no. What makes you think that? Have you measured it? – Bergi Jan 19 '21 at 12:42
  • 1
    "*`Map.has()` will not recognise the JSON object due to the `__proto__` parameter.*" - no, the prototype chain has nothing to do with that. It's just that **objects** are compared by reference. See e.g. https://stackoverflow.com/q/37187648/1048572 or https://stackoverflow.com/q/32660188/1048572 – Bergi Jan 19 '21 at 12:43
  • I have not measured this yet. Will do some measurement with fast-json-stringify and also checking value equality with lo-dash. Thanks for the pointers. – Dennis Bauszus Jan 19 '21 at 18:28

1 Answers1

-1

The below answer uses sha1 hashing. There is a theoretical chance of a hash collision, but for all practical purposes it is not going to happen and you shouldn't have to worry about it.


We use the memoize pattern quite a bit and use hashing instead of JSON.stringify. We settled on the following pattern

// import objectHashStrict from 'object-hash-strict';
// import lruCacheExt from 'lru-cache-ext';

const cache = new lruCacheExt({ ttl: 24 * 60 * 1000, max: 100 });

const get = (input) => cache.memoizeSync(objectHashStrict(input.style), () => {
  // do stuff here as necessary
  return {
    name: input.name,
    ...input.style,
    stroke: {
      color: '#ff0000',
      width: 1
    }
  };
});

console.log(get({ name: 'Alpha', style: { type: 'One' } }));
// => { name: 'Alpha', type: 'One', stroke: { color: '#ff0000', width: 1 } }
console.log(get({ name: 'Beta', style: { type: 'One' } }));
// => { name: 'Alpha', type: 'One', stroke: { color: '#ff0000', width: 1 } }
console.log(get({ name: 'Zeta', style: { type: 'Two' } }));
// => { name: 'Zeta', type: 'Two', stroke: { color: '#ff0000', width: 1 } }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-hash-strict@3.0.1"></script>
<script src="https://bundle.run/lru-cache-ext@3.0.2"></script>

Disclaimer: I'm the author of lru-cache-ext and object-hash-strict

vincent
  • 1,953
  • 3
  • 18
  • 24
  • If you know which keys are important from the style object, you could also just pass just those into the hashing util. – vincent Jan 19 '21 at 19:06
  • This would return the wrong values if you passed two different objects that hash to the same value. Am I missing something? – Bergi Jan 19 '21 at 20:07
  • @Bergi Are you talking about a hash collision? That should never happen. On the other hand, what about `JSON.stringify({a: 1, b: 2}) !== JSON.stringify({b: 2, a: 1})`? I am very confused now – vincent Jan 19 '21 at 21:27
  • Hash collisions might be rare, but they do happen and must be accounted for. – Bergi Jan 19 '21 at 22:17
  • @Bergi Sure, they do. And they don't. We can get into a theoretical argument here, but for all practical purposes a sha1 collision (used by object-hash) is not going to happen https://security.googleblog.com/2017/02/announcing-first-sha1-collision.html -- I've updated the answer to reflect your concerns. I'd be much more worried about something like `JSON.stringify(NaN) === JSON.stringify(null)` – vincent Jan 19 '21 at 22:24