Spaces:
Sleeping
Sleeping
File size: 6,853 Bytes
e7b2eb4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | // @flow
import {extend} from '../util/util';
import Tile from './tile';
import type {FeatureState} from '../style-spec/expression';
export type FeatureStates = {[feature_id: string]: FeatureState};
export type LayerFeatureStates = {[layer: string]: FeatureStates};
/**
* SourceFeatureState manages the state and pending changes
* to features in a source, separated by source layer.
* stateChanges and deletedStates batch all changes to the tile (updates and removes, respectively)
* between coalesce() events. addFeatureState() and removeFeatureState() also update their counterpart's
* list of changes, such that coalesce() can apply the proper state changes while agnostic to the order of operations.
* In deletedStates, all null's denote complete removal of state at that scope
* @private
*/
class SourceFeatureState {
state: LayerFeatureStates;
stateChanges: LayerFeatureStates;
deletedStates: {};
constructor() {
this.state = {};
this.stateChanges = {};
this.deletedStates = {};
}
updateState(sourceLayer: string, featureId: number | string, newState: Object) {
const feature = String(featureId);
this.stateChanges[sourceLayer] = this.stateChanges[sourceLayer] || {};
this.stateChanges[sourceLayer][feature] = this.stateChanges[sourceLayer][feature] || {};
extend(this.stateChanges[sourceLayer][feature], newState);
if (this.deletedStates[sourceLayer] === null) {
this.deletedStates[sourceLayer] = {};
for (const ft in this.state[sourceLayer]) {
if (ft !== feature) this.deletedStates[sourceLayer][ft] = null;
}
} else {
const featureDeletionQueued = this.deletedStates[sourceLayer] && this.deletedStates[sourceLayer][feature] === null;
if (featureDeletionQueued) {
this.deletedStates[sourceLayer][feature] = {};
for (const prop in this.state[sourceLayer][feature]) {
if (!newState[prop]) this.deletedStates[sourceLayer][feature][prop] = null;
}
} else {
for (const key in newState) {
const deletionInQueue = this.deletedStates[sourceLayer] && this.deletedStates[sourceLayer][feature] && this.deletedStates[sourceLayer][feature][key] === null;
if (deletionInQueue) delete this.deletedStates[sourceLayer][feature][key];
}
}
}
}
removeFeatureState(sourceLayer: string, featureId?: number | string, key?: string) {
const sourceLayerDeleted = this.deletedStates[sourceLayer] === null;
if (sourceLayerDeleted) return;
const feature = String(featureId);
this.deletedStates[sourceLayer] = this.deletedStates[sourceLayer] || {};
if (key && featureId !== undefined) {
if (this.deletedStates[sourceLayer][feature] !== null) {
this.deletedStates[sourceLayer][feature] = this.deletedStates[sourceLayer][feature] || {};
this.deletedStates[sourceLayer][feature][key] = null;
}
} else if (featureId !== undefined) {
const updateInQueue = this.stateChanges[sourceLayer] && this.stateChanges[sourceLayer][feature];
if (updateInQueue) {
this.deletedStates[sourceLayer][feature] = {};
for (key in this.stateChanges[sourceLayer][feature]) this.deletedStates[sourceLayer][feature][key] = null;
} else {
this.deletedStates[sourceLayer][feature] = null;
}
} else {
this.deletedStates[sourceLayer] = null;
}
}
getState(sourceLayer: string, featureId: number | string) {
const feature = String(featureId);
const base = this.state[sourceLayer] || {};
const changes = this.stateChanges[sourceLayer] || {};
const reconciledState = extend({}, base[feature], changes[feature]);
//return empty object if the whole source layer is awaiting deletion
if (this.deletedStates[sourceLayer] === null) return {};
else if (this.deletedStates[sourceLayer]) {
const featureDeletions = this.deletedStates[sourceLayer][featureId];
if (featureDeletions === null) return {};
for (const prop in featureDeletions) delete reconciledState[prop];
}
return reconciledState;
}
initializeTileState(tile: Tile, painter: any) {
tile.setFeatureState(this.state, painter);
}
coalesceChanges(tiles: {[_: any]: Tile}, painter: any) {
//track changes with full state objects, but only for features that got modified
const featuresChanged: LayerFeatureStates = {};
for (const sourceLayer in this.stateChanges) {
this.state[sourceLayer] = this.state[sourceLayer] || {};
const layerStates = {};
for (const feature in this.stateChanges[sourceLayer]) {
if (!this.state[sourceLayer][feature]) this.state[sourceLayer][feature] = {};
extend(this.state[sourceLayer][feature], this.stateChanges[sourceLayer][feature]);
layerStates[feature] = this.state[sourceLayer][feature];
}
featuresChanged[sourceLayer] = layerStates;
}
for (const sourceLayer in this.deletedStates) {
this.state[sourceLayer] = this.state[sourceLayer] || {};
const layerStates = {};
if (this.deletedStates[sourceLayer] === null) {
for (const ft in this.state[sourceLayer]) {
layerStates[ft] = {};
this.state[sourceLayer][ft] = {};
}
} else {
for (const feature in this.deletedStates[sourceLayer]) {
const deleteWholeFeatureState = this.deletedStates[sourceLayer][feature] === null;
if (deleteWholeFeatureState) this.state[sourceLayer][feature] = {};
else {
for (const key of Object.keys(this.deletedStates[sourceLayer][feature])) {
delete this.state[sourceLayer][feature][key];
}
}
layerStates[feature] = this.state[sourceLayer][feature];
}
}
featuresChanged[sourceLayer] = featuresChanged[sourceLayer] || {};
extend(featuresChanged[sourceLayer], layerStates);
}
this.stateChanges = {};
this.deletedStates = {};
if (Object.keys(featuresChanged).length === 0) return;
for (const id in tiles) {
const tile = tiles[id];
tile.setFeatureState(featuresChanged, painter);
}
}
}
export default SourceFeatureState;
|