UNPKG

@ngageoint/mage.arcgis.service

Version:

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

465 lines 19 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FeatureServiceAdmin = void 0; const entities_events_forms_1 = require("@ngageoint/mage.service/lib/entities/events/entities.events.forms"); const ObservationsTransformer_1 = require("./ObservationsTransformer"); const form_data_1 = __importDefault(require("form-data")); const arcgis_rest_request_1 = require("@esri/arcgis-rest-request"); /** * Administers hosted feature services such as layer creation and updates. */ class FeatureServiceAdmin { /** * Constructor. * @param {ArcGISPluginConfig} config The plugins configuration. * @param {ArcGISIdentityService} identityService The identity service. * @param {Console} console Used to log to the console. */ constructor(config, identityService, console) { this._config = config; this._identityService = identityService; this._console = console; } /** * Create the layer * @param {FeatureServiceConfig} service feature service * @param {FeatureLayerConfig} featureLayer feature layer * @param {number} nextId next service layer id * @param {MageEventRepository} eventRepo event repository * @returns {Promise<number>} layer id */ createLayer(service, featureLayer, nextId, eventRepo) { return __awaiter(this, void 0, void 0, function* () { // TODO: Is this function needed? This allows the server to create a feature layer on the arcgis server. this._console.info('FeatureServiceAdmin createLayer()'); const layer = { type: 'Feature Layer' }; const layerIdentifier = featureLayer.layer; const layerIdentifierNumber = Number(layerIdentifier); if (isNaN(layerIdentifierNumber)) { layer.name = String(layerIdentifier); layer.id = nextId; } else { layer.id = layerIdentifierNumber; } const events = yield this.layerEvents(featureLayer, eventRepo); if (layer.name == null) { layer.name = this.layerName(events); } if (featureLayer.geometryType != null) { layer.geometryType = featureLayer.geometryType; } else { layer.geometryType = 'esriGeometryPoint'; } layer.fields = this.fields(events); // TODO What other layer properties are needed or required? // https://developers.arcgis.com/rest/services-reference/online/add-to-definition-feature-service-.htm#GUID-63F2BD08-DCF4-485D-A3E6-C7116E17DDD8 this.create(service, layer); return layer.id; }); } /** * Update the layer fields * @param {FeatureServiceConfig} service feature service * @param {FeatureLayerConfig} featureLayer feature layer * @param {LayerInfoResult} layerInfo layer info * @param {MageEventRepository} eventRepo event repository */ updateLayer(service, featureLayer, layerInfo, eventRepo) { return __awaiter(this, void 0, void 0, function* () { this._console.info('FeatureServiceAdmin updateLayer()'); const events = yield this.layerEvents(featureLayer, eventRepo); const promises = []; const eventFields = this.fields(events); const layerFields = layerInfo.fields; const layerFieldSet = new Set(); for (const field of layerFields) { layerFieldSet.add(field.name); } const addFields = []; for (const field of eventFields) { if (!layerFieldSet.has(field.name)) { addFields.push(field); const layerField = {}; layerField.name = field.name; layerField.editable = true; layerFields.push(layerField); } } if (addFields.length > 0) { promises.push(this.addFields(service, layerInfo.id, addFields)); } const eventFieldSet = new Set(); for (const field of eventFields) { eventFieldSet.add(field.name); } const deleteFields = []; const remainingFields = []; for (const field of layerFields) { if (field.editable && !eventFieldSet.has(field.name)) { deleteFields.push(field); } else { remainingFields.push(field); } } if (deleteFields.length > 0) { layerInfo.fields = remainingFields; promises.push(this.deleteFields(service, layerInfo.id, deleteFields)); } yield Promise.all(promises); return eventFields; }); } /** * Get the Mage layer events * @param {FeatureLayerConfig} layer feature layer * @param {MageEventRepository} eventRepo event repository * @returns {Promise<MageEvent[]>} Mage layer events */ layerEvents(layer, eventRepo) { return __awaiter(this, void 0, void 0, function* () { const layerEventIds = new Set(); if (layer.eventIds != null) { for (const layerEventId of layer.eventIds) { layerEventIds.add(layerEventId); } } let mageEvents; if (layerEventIds.size > 0) { mageEvents = yield eventRepo.findAll(); } else { mageEvents = yield eventRepo.findActiveEvents(); } const events = []; for (const mageEvent of mageEvents) { if (layerEventIds.size === 0 || layerEventIds.has(mageEvent.id)) { const event = yield eventRepo.findById(mageEvent.id); if (event != null) { events.push(event); } } } return events; }); } /** * Create a layer name * @param {MageEvent[]} events layer events * @returns {string} layer name */ layerName(events) { let layerName = ''; for (let i = 0; i < events.length; i++) { if (i > 0) { layerName += ', '; } layerName += events[i].name; } return layerName; } /** * Builder the layer fields * @param {MageEvent[]} events layer events * @returns {Field[]} fields */ fields(events) { const fields = []; fields.push(this.createTextField(this._config.observationIdField)); if (this._config.eventIdField != null) { fields.push(this.createIntegerField(this._config.eventIdField)); } if (this._config.eventNameField != null) { fields.push(this.createTextField(this._config.eventNameField)); } if (this._config.userIdField != null) { fields.push(this.createTextField(this._config.userIdField)); } if (this._config.usernameField != null) { fields.push(this.createTextField(this._config.usernameField)); } if (this._config.userDisplayNameField != null) { fields.push(this.createTextField(this._config.userDisplayNameField)); } if (this._config.deviceIdField != null) { fields.push(this.createTextField(this._config.deviceIdField)); } if (this._config.createdAtField != null) { fields.push(this.createDateTimeField(this._config.createdAtField)); } if (this._config.lastModifiedField != null) { fields.push(this.createDateTimeField(this._config.lastModifiedField)); } if (this._config.geometryType != null) { fields.push(this.createTextField(this._config.geometryType)); } const fieldNames = new Set(); for (const field of fields) { fieldNames.add(field.name); } this.eventsFields(events, fields, fieldNames); return fields; } /** * Create a field * @param {string} name field name * @param {FormFieldType} type form field type * @param {boolean} [integer] integer flag when numeric * @returns {Field} field */ createField(name, type, integer) { const field = this.initField(type, integer); if (field != null) { field.name = ObservationsTransformer_1.ObservationsTransformer.replaceSpaces(name); field.alias = field.name; field.editable = true; } return field; } /** * Create a text field * @param {string} name field name * @returns {Field} field */ createTextField(name) { return this.createField(name, entities_events_forms_1.FormFieldType.Text); } /** * Create a numeric field * @param {string} name field name * @param {boolean} [integer] integer flag * @returns {Field} field */ createNumericField(name, integer) { return this.createField(name, entities_events_forms_1.FormFieldType.Numeric, integer); } /** * Create an integer field * @param {string} name field name * @returns {Field} field */ createIntegerField(name) { return this.createNumericField(name, true); } /** * Create a date time field * @param {string} name field name * @returns {Field} field */ createDateTimeField(name) { return this.createField(name, entities_events_forms_1.FormFieldType.DateTime); } /** * Build fields from the layer events * @param {MageEvent[]} events layer events * @param {Field[]} fields created fields * @param {Set<string>} fieldNames set of all field names */ eventsFields(events, fields, fieldNames) { const forms = new Set(); for (const event of events) { this.eventFields(event, forms, fields, fieldNames); } } /** * Build fields from the layer event * @param {MageEvent} event layer event * @param {Set<FormId>} forms set of processed forms * @param {Field[]} fields created fields * @param {Set<string>} fieldNames set of all field names */ eventFields(event, forms, fields, fieldNames) { for (const form of event.activeForms) { if (!forms.has(form.id)) { forms.add(form.id); for (const formField of form.fields) { if (!formField.archived) { this.createFormField(form, formField, fields, fieldNames); } } } } } /** * Build a field from the form field * @param {Form} form form * @param {FormField} formField form field * @param {Field[]} fields created fields * @param {Set<string>} fieldNames set of all field names */ createFormField(form, formField, fields, fieldNames) { const field = this.initField(formField.type); if (field != null) { const sanitizedName = ObservationsTransformer_1.ObservationsTransformer.replaceSpaces(formField.title); const sanitizedFormName = ObservationsTransformer_1.ObservationsTransformer.replaceSpaces(form.name); const name = `${sanitizedFormName}_${sanitizedName}`.toLowerCase(); fieldNames.add(name); field.name = name; field.alias = field.name; field.editable = true; field.defaultValue = formField.value; fields.push(field); } } /** * Initialize a field by type * @param {FormFieldType} type form field type * @param {boolean} [integer] numeric integer field type * @returns {Field | null} field or null */ initField(type, integer) { const field = {}; switch (type) { case entities_events_forms_1.FormFieldType.CheckBox: case entities_events_forms_1.FormFieldType.Dropdown: case entities_events_forms_1.FormFieldType.Email: case entities_events_forms_1.FormFieldType.MultiSelectDropdown: case entities_events_forms_1.FormFieldType.Password: case entities_events_forms_1.FormFieldType.Radio: case entities_events_forms_1.FormFieldType.Text: field.type = 'esriFieldTypeString'; field.actualType = 'nvarchar'; field.sqlType = 'sqlTypeNVarchar'; field.length = this._config.textFieldLength; break; case entities_events_forms_1.FormFieldType.TextArea: field.type = 'esriFieldTypeString'; field.actualType = 'nvarchar'; field.sqlType = 'sqlTypeNVarchar'; field.length = this._config.textAreaFieldLength; break; case entities_events_forms_1.FormFieldType.DateTime: field.type = 'esriFieldTypeDate'; field.sqlType = 'sqlTypeOther'; field.length = 10; break; case entities_events_forms_1.FormFieldType.Numeric: if (integer) { field.type = 'esriFieldTypeInteger'; field.actualType = 'int'; field.sqlType = 'sqlTypeInteger'; } else { field.type = 'esriFieldTypeDouble'; field.actualType = 'float'; field.sqlType = 'sqlTypeFloat'; } break; case entities_events_forms_1.FormFieldType.Geometry: case entities_events_forms_1.FormFieldType.Attachment: case entities_events_forms_1.FormFieldType.Hidden: default: break; } return field.type != null ? field : null; } /** * Create the feature layer * @param {FeatureServiceConfig} service feature service * @param {Layer} layer layer */ create(service, layer) { return __awaiter(this, void 0, void 0, function* () { const url = this.adminUrl(service) + 'addToDefinition'; this._console.info('ArcGIS feature service addToDefinition (create feature layer) url ' + url); const form = new form_data_1.default(); form.append('addToDefinition', JSON.stringify(layer)); const identityManager = yield this._identityService.signin(service); yield (0, arcgis_rest_request_1.request)(url, { authentication: identityManager, httpMethod: 'POST', params: form }).catch((error) => this._console.error('FeatureServiceAdmin create() error ' + error)); }); } /** * Add fields to the layer * @param {FeatureLayerConfig} service feature service * @param {FeatureLayerConfig} featureLayer feature layer * @param {Field[]} fields fields to add */ addFields(service, featureLayer, fields) { return __awaiter(this, void 0, void 0, function* () { const layer = { fields: fields }; const url = this.adminUrl(service) + featureLayer.toString() + '/addToDefinition'; this._console.info('ArcGIS feature layer addToDefinition (add fields) url ' + url); try { const identityManager = yield this._identityService.signin(service); yield (0, arcgis_rest_request_1.request)(url, { authentication: identityManager, params: { addToDefinition: layer, f: "json" } }).catch((error) => { this._console.error('Error in addFields: ' + error); }); } catch (error) { this._console.error('FeatureServiceAdmin addFields() error ' + error); } }); } /** * Delete fields from the layer * @param {FeatureServiceConfig} service feature service * @param {FeatureLayerConfig} featureLayer feature layer * @param {LayerField[]} fields fields to delete */ deleteFields(service, featureLayer, fields) { return __awaiter(this, void 0, void 0, function* () { const deleteFields = []; for (const layerField of fields) { const field = {}; field.name = layerField.name; deleteFields.push(field); } const layer = {}; layer.fields = deleteFields; const url = this.adminUrl(service) + featureLayer.toString() + '/deleteFromDefinition'; this._console.info('ArcGIS feature layer deleteFromDefinition (delete fields) url ' + url); try { const identityManager = yield this._identityService.signin(service); yield (0, arcgis_rest_request_1.request)(url, { authentication: identityManager, httpMethod: 'POST', params: { deleteFromDefinition: layer, f: 'json' } }).catch((error) => this._console.error('FeatureServiceAdmin deleteFields() error ' + error)); } catch (error) { this._console.error('FeatureServiceAdmin deleteFields() error ' + error); } }); } /** * Get the administration url * @param {FeatureServiceConfig} service feature service * @returns {string} url */ adminUrl(service) { let url = service.url.replace('/services/', '/admin/services/'); if (!url.endsWith('/')) { url += '/'; } return url; } } exports.FeatureServiceAdmin = FeatureServiceAdmin; //# sourceMappingURL=FeatureServiceAdmin.js.map