UNPKG

@ngageoint/mage.arcgis.service

Version:

A mage service plugin that synchronizes mage observations to a configured ArcGIS feature layer.

309 lines 16.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ObservationProcessor = void 0; const ArcGISPluginConfig_1 = require("./types/ArcGISPluginConfig"); const ObservationsTransformer_1 = require("./ObservationsTransformer"); const ArcObjects_1 = require("./ArcObjects"); const FeatureService_1 = require("./FeatureService"); const LayerInfo_1 = require("./LayerInfo"); const FeatureLayerProcessor_1 = require("./FeatureLayerProcessor"); const EventTransform_1 = require("./EventTransform"); const GeometryChangedHandler_1 = require("./GeometryChangedHandler"); const EventDeletionHandler_1 = require("./EventDeletionHandler"); const EventLayerProcessorOrganizer_1 = require("./EventLayerProcessorOrganizer"); const FeatureServiceAdmin_1 = require("./FeatureServiceAdmin"); /** * Class that wakes up at a certain configured interval and processes any new observations that can be * sent to any specified ArcGIS feature layers. */ class ObservationProcessor { /** * Constructor. * @param {PluginStateRepository<ArcGISPluginConfig>} stateRepo The plugins configuration. * @param {MageEventRepository} eventRepo Used to get all the active events. * @param {ObservationRepositoryForEvent} obsRepos Used to get new observations. * @param {UserRepository} userRepo Used to get user information. * @param {ArcGISIdentityService} identityService Used to manager ArcGIS user identities. * @param {Console} console Used to log to the console. */ constructor(stateRepo, eventRepo, obsRepos, userRepo, identityService, console) { /** * True if the processor is currently active, false otherwise. */ this._isRunning = false; /** * Sends observations to a single feature layer. */ this._layerProcessors = []; /** * True if this is a first run at updating arc feature layers. If so we need to make sure the layers are * all up to date. */ this._firstRun = true; this._stateRepo = stateRepo; this._eventRepo = eventRepo; this._obsRepos = obsRepos; this._userRepo = userRepo; this._identityService = identityService; this._console = console; this._organizer = new EventLayerProcessorOrganizer_1.EventLayerProcessorOrganizer(); this._transformer = new ObservationsTransformer_1.ObservationsTransformer(ArcGISPluginConfig_1.defaultArcGISPluginConfig, console); this._geometryChangeHandler = new GeometryChangedHandler_1.GeometryChangedHandler(this._transformer); this._eventDeletionHandler = new EventDeletionHandler_1.EventDeletionHandler(this._console, ArcGISPluginConfig_1.defaultArcGISPluginConfig); } /** * Gets the current configuration from the database. * @returns {Promise<ArcGISPluginConfig>} The current configuration from the database. */ safeGetConfig() { return __awaiter(this, void 0, void 0, function* () { const state = yield this._stateRepo.get(); if (!state) return yield this._stateRepo.put(ArcGISPluginConfig_1.defaultArcGISPluginConfig); return yield this._stateRepo.get().then((state) => state ? state : this._stateRepo.put(ArcGISPluginConfig_1.defaultArcGISPluginConfig)); }); } /** * Puts a new confguration in the state repo. * @param {ArcGISPluginConfig} newConfig The new config to put into the state repo. * @returns {Promise<ArcGISPluginConfig>} The updated configuration. */ putConfig(newConfig) { return __awaiter(this, void 0, void 0, function* () { return yield this._stateRepo.put(newConfig); }); } /** * Updates the confguration in the state repo. * @param {ArcGISPluginConfig} newConfig The new config to put into the state repo. * @returns {Promise<ArcGISPluginConfig>} The updated configuration. */ patchConfig(newConfig) { return __awaiter(this, void 0, void 0, function* () { return yield this._stateRepo.patch(newConfig); }); } /** * Gets the current configuration and updates the processor if needed * @returns {Promise<ArcGISPluginConfig>} The current configuration from the database. */ updateConfig() { return __awaiter(this, void 0, void 0, function* () { const config = yield this.safeGetConfig(); if (!config.enabled) { this._console.info('ArcGIS plugin is disabled, stopping processor'); this.stop(); return config; } // Include configured eventform definitions while detecting changes in config const eventIds = config.featureServices .flatMap(service => service.layers) .flatMap(layer => layer.eventIds) .filter((eventId) => typeof eventId === 'number'); const eventForms = yield this._eventRepo.findAllByIds(eventIds); const fullConfig = Object.assign(Object.assign({}, config), { eventForms }); const configJson = JSON.stringify(fullConfig); if (this._previousConfig == null || this._previousConfig !== configJson) { this._transformer = new ObservationsTransformer_1.ObservationsTransformer(config, console); this._geometryChangeHandler = new GeometryChangedHandler_1.GeometryChangedHandler(this._transformer); this._eventDeletionHandler.updateConfig(config); this._layerProcessors = []; this._previousConfig = configJson; yield this.getFeatureServiceLayers(config); } return config; }); } /** * Starts the processor. */ start() { return __awaiter(this, void 0, void 0, function* () { this._isRunning = true; this._firstRun = true; yield this.processAndScheduleNext(); }); } /** * Stops the processor. */ stop() { this._isRunning = false; clearTimeout(this._nextTimeout); } /** * Gets information on all the configured features service layers. * @param {ArcGISPluginConfig} config The plugins configuration. */ getFeatureServiceLayers(config) { return __awaiter(this, void 0, void 0, function* () { const promises = []; for (const service of config.featureServices) { promises.push((() => __awaiter(this, void 0, void 0, function* () { try { this._console.info(`Getting feature service layers for ${service.url}`); const identityManager = yield this._identityService.signin(service); const featureService = new FeatureService_1.FeatureService(console, service, identityManager); const arcService = yield featureService.getService(); for (const featureLayer of arcService.layers) { const featureLayerConfig = service.layers.find(layer => layer.layer.toString() === featureLayer.name.toString()); if (featureLayerConfig) { const url = `${service.url}/${featureLayer.id}`; const layerInfo = yield featureService.getLayer(featureLayer.id); if (featureLayer.geometryType != null) { // TODO The featureLayerConfig should contain the layer id featureLayerConfig.layer = featureLayer.id; const admin = new FeatureServiceAdmin_1.FeatureServiceAdmin(config, this._identityService, this._console); const eventIds = featureLayerConfig.eventIds || []; const layerFields = yield admin.updateLayer(service, featureLayerConfig, layerInfo, this._eventRepo); const info = new LayerInfo_1.LayerInfo(url, eventIds, Object.assign(Object.assign({}, layerInfo), { fields: layerFields })); const layerProcessor = new FeatureLayerProcessor_1.FeatureLayerProcessor(info, config, identityManager, this._console); this._layerProcessors.push(layerProcessor); } } } } catch (err) { this._console.error(`Error getting feature service layers for ${service.url}:`, err); } }))()); } yield Promise.all(promises); }); } /** * Processes any new observations and then schedules its next run if it hasn't been stopped. */ processAndScheduleNext() { return __awaiter(this, void 0, void 0, function* () { const config = yield this.updateConfig(); if (this._isRunning) { if (config.enabled && this._layerProcessors.length > 0) { this._console.info('ArcGIS plugin checking for any pending updates or adds'); const pendingPromises = []; for (const layerProcessor of this._layerProcessors) { pendingPromises.push(layerProcessor.processPendingUpdates()); } yield Promise.all(pendingPromises); this._console.info('ArcGIS plugin processing new observations...'); const enabledEvents = (yield this._eventRepo.findActiveEvents()).filter(event => this._layerProcessors.some(layerProcessor => layerProcessor.layerInfo.hasEvent(event.id))); this._eventDeletionHandler.checkForEventDeletion(enabledEvents, this._layerProcessors, this._firstRun); const eventsToProcessors = this._organizer.organize(enabledEvents, this._layerProcessors); const nextQueryTime = Date.now(); const promises = []; for (const pair of eventsToProcessors) { promises.push(new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { this._console.info('ArcGIS getting newest observations for event ' + pair.event.name); const obsRepo = yield this._obsRepos(pair.event.id); const pagingSettings = { pageSize: config.batchSize, pageIndex: 0, includeTotalCount: true }; let morePages = true; let numberLeft = 0; while (morePages) { numberLeft = yield this.queryAndSend(config, pair.featureLayerProcessors, obsRepo, pagingSettings, numberLeft); morePages = numberLeft > 0; } resolve(); }))); } yield Promise.all(promises); for (const layerProcessor of this._layerProcessors) { layerProcessor.lastTimeStamp = nextQueryTime; } this._firstRun = false; // ArcGISIndentityManager access tokens may have been updated check and save this._identityService.updateIndentityManagers(); } this.scheduleNext(config); } }); } scheduleNext(config) { if (this._isRunning) { let interval = config.intervalSeconds; if (this._firstRun && config.featureServices.length > 0) { interval = config.startupIntervalSeconds; } else { for (const layerProcessor of this._layerProcessors) { if (layerProcessor.hasPendingUpdates()) { interval = config.updateIntervalSeconds; break; } } } this._nextTimeout = setTimeout(() => { this.processAndScheduleNext(); }, interval * 1000); } } /** * Queries for new observations and sends them to any configured arc servers. * @param {ArcGISPluginConfig} config The plugin configuration. * @param {FeatureLayerProcessor[]} layerProcessors The layer processors to use when processing arc objects. * @param {EventScopedObservationRepository} obsRepo The observation repo for an event. * @param {PagingParameters} pagingSettings Current paging settings. * @param {number} numberLeft The number of observations left to query and send to arc. * @returns {Promise<number>} The number of observations still needing to be queried and sent to arc. */ queryAndSend(config, layerProcessors, obsRepo, pagingSettings, numberLeft) { return __awaiter(this, void 0, void 0, function* () { let newNumberLeft = numberLeft; let queryTime = -1; for (const layerProcessor of layerProcessors) { if (queryTime === -1 || layerProcessor.lastTimeStamp < queryTime) { queryTime = layerProcessor.lastTimeStamp; } } const latestObs = yield obsRepo.findLastModifiedAfter(queryTime, pagingSettings); if ((latestObs === null || latestObs === void 0 ? void 0 : latestObs.totalCount) != null && latestObs.totalCount > 0) { if (pagingSettings.pageIndex === 0) { this._console.info('ArcGIS newest observation count ' + latestObs.totalCount); newNumberLeft = latestObs.totalCount; } const observations = latestObs.items; const mageEvent = yield this._eventRepo.findById(obsRepo.eventScope); const eventTransform = new EventTransform_1.EventTransform(config, mageEvent); const arcObjects = new ArcObjects_1.ArcObjects(); this._geometryChangeHandler.checkForGeometryChange(observations, arcObjects, layerProcessors, this._firstRun); for (const observation of observations) { // TODO: Should archived observations be removed after a certain time? Also this uses 'startsWith' because not all deleted observations use 'archived' which is a bug if (observation.states.length > 0 && observation.states[0].name.startsWith('archive')) { const arcObservation = this._transformer.createObservation(observation); arcObjects.deletions.push(arcObservation); } else { let user = null; if (observation.userId != null) { user = yield this._userRepo.findById(observation.userId); } const arcObservation = this._transformer.transform(observation, eventTransform, user); arcObjects.add(arcObservation); } } arcObjects.firstRun = this._firstRun; for (const layerProcessor of layerProcessors) { layerProcessor.processArcObjects(arcObjects); } newNumberLeft -= latestObs.items.length; pagingSettings.pageIndex++; } else { this._console.info('ArcGIS no new observations'); } return newNumberLeft; }); } } exports.ObservationProcessor = ObservationProcessor; //# sourceMappingURL=ObservationProcessor.js.map