Skip to content

Commit

Permalink
#313 mmgisAPI addLayer and removeLayer
Browse files Browse the repository at this point in the history
  • Loading branch information
tariqksoliman committed Feb 7, 2023
1 parent fefa3e3 commit ed937d7
Show file tree
Hide file tree
Showing 5 changed files with 395 additions and 20 deletions.
113 changes: 113 additions & 0 deletions docs/pages/APIs/JavaScript/Main/Main.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ The `src/essence/mmgisAPI/mmgisAPI.js` file exposes functions that can be called
#### _Contents_

- [Layer Control](#layer-control)
- [addLayer(layerObj, placement)](#addlayerlayerobj-placement)
- [removeLayer(layerUUID)](#removelayerlayeruuid)
- [clearVectorLayer(layerUUID)](#clearvectorlayerlayeruuid)
- [updateVectorLayer(layerUUID, inputData)](#updatevectorlayerlayeruuid-inputdata)
- [trimVectorLayerKeepBeforeTime(layerUUID, keepBeforeTime, timePropPath)](#trimvectorlayerkeepbeforetimelayeruuid-keepbeforetime-timeproppath)
Expand Down Expand Up @@ -59,6 +61,117 @@ The `src/essence/mmgisAPI/mmgisAPI.js` file exposes functions that can be called

## Layer Control

### addLayer(layerObj, placement)

Adds a layer to the map. This adds a layer to client as if it were coming from the configuration. This layer becomes accessible via the LayersTool and well as on the globe. This does not add the layer to the mission's configuration.

For a more "temporary" layer, use Leaflet directly through `L.{leafletLayer}.addTo(mmgisAPI.map)`. See [here](https://leafletjs.com/reference.html) for reference on how to construct Leafletjs layers.

#### Function parameters

- `layerObj` - the full mmgis layer object configuration (`GET http://localhost:8889/api/configure/get?mission={mission}` to get sample layer objects from the existing configuration). At minimum `layerObj.name` and `layerObj.type` are required.
- `layerObj.name` - A unique name/uuid for the layer
- `layerObj.type` - One of the support MMGIS layer types: `data, header, model, query, tile, or vector`
- `placement` - _optionaL_ - Where in the list/tree to add this layer relative to other layers.
- `placement.path` - A path to a header in 'layers' to place the new layer. A simple path ('sublayers' are added). Defaults to no group. Use a dot-notated string for nestings.
- `placement.index` - Index in 'layers' (or path) to place the new layer. Out of range placement indices are best fit.

Returns a `Promise` when the layer is added and loaded.

The following is an example of how to call the `addLayer` function:

```javascript
window.mmgisAPI.addLayer({ name: "Sample Header", type: "header" }).then(() => {
console.log("added and loaded");
});

window.mmgisAPI
.addLayer(
{
name: "New Waypoints",
kind: "waypoint",
shape: "none",
type: "vector",
url: "Layers/Waypoints/waypoints.json",
demparser: "",
controlled: false,
layer3dType: "clamped",
description:
"### Overview\n\nIure iure quas doloremque sequi pariatur repudiandae. Provident similique in illum deleniti qui consequuntur iste aut. Quia accusamus dolorem beatae et aut.\n\nVero cum ullam cumque optio laborum. Qui corporis incidunt accusamus voluptatem. Quam eos et expedita. Quidem et velit fuga et delectus veniam.\n\n- Vel ex voluptatem dicta\n- Dolor et itaque quidem\n- Vero cum ullam cumque [optio laborum](www.duckduckgo.com)\n\nDolor et itaque quidem. Dolorem ut nemo porro rerum. Rerum voluptas quo sit velit voluptatibus perspiciatis ipsum. Vel ex voluptatem dicta. Et porro harum maiores. Quae consequatur exercitationem numquam.\n",
tags: ["rover", "science", "telem:45B", "location"],
legend: "Layers/Waypoints/legend.csv",
visibility: true,
initialOpacity: 1,
togglesWithHeader: true,
style: {
className: "waypoints",
color: "#FFF",
fillColor: "#000",
weight: 2,
fillOpacity: 1,
opacity: 1,
},
variables: {
useKeyAsName: "sol_site_p",
layerAttachments: {
labels: {
initialVisibility: false,
theme: "default",
size: "large",
},
},
markerAttachments: {
bearing: {
angleProp: "yaw_rad",
angleUnit: "rad",
color: "#FFFFFF",
},
},
search: "(sol_site_p)",
},
radius: 8,
time: {
enabled: false,
type: "requery",
isRelative: true,
current: "2023-02-07T17:58:53Z",
start: "",
end: "",
startProp: "",
endProp: "",
format: "%Y-%m-%dT%H:%M:%SZ",
compositeTile: false,
refresh: "1 hours",
increment: "5 minutes",
},
uuid: "7f6396c3-eef1-401a-9e99-790ed102efff",
},
{ path: "Features", index: 0 }
)
.then(() => {
console.log("loaded");
})
.catch((errMsg) => {
console.log(errMsg);
});
```

### removeLayer(layerUUID)

Removes a layer from the map.

#### Function parameters

- `layerUUID` - The name/uuid of the layer to remove.

Returns `true` if found and removed, otherwise `false`

The following is an example of how to call the `removeLayer` function:

```javascript
window.mmgisAPI.removeLayer("Sample Header"); // => true
```

### clearVectorLayer(layerUUID)

This function clears an existing vector layer with a specified name
Expand Down
107 changes: 107 additions & 0 deletions src/essence/Basics/Formulae_/Formulae_.js
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,113 @@ var Formulae_ = {
return '#ffffff'
}
},
getIn4Layers: function (obj, keyArray, notSetValue, assumeLayerHierarchy) {
if (obj == null) return notSetValue != null ? notSetValue : null
if (keyArray == null) return notSetValue != null ? notSetValue : null
if (typeof keyArray === 'string') keyArray = keyArray.split('.')
let object = Object.assign({}, obj)
console.log(object, keyArray)
for (let i = 0; i < keyArray.length; i++) {
if (object && object.hasOwnProperty(keyArray[i]))
object = object[keyArray[i]]
else if (
assumeLayerHierarchy &&
object &&
Formulae_.objectArrayIndexOfKeyWithValue(
object,
'name',
keyArray[i]
) >= 0
)
object =
object[
Formulae_.objectArrayIndexOfKeyWithValue(
object,
'name',
keyArray[i]
)
]
else return notSetValue != null ? notSetValue : null
}
return object
},
objectArrayIndexOfKeyWithValue: function (objectArray, key, value) {
var index = -1
for (let i in objectArray) {
if (objectArray[i]) {
if (
objectArray[i].hasOwnProperty(key) &&
objectArray[i][key] === value
) {
index = i
break
}
}
}
return index
},
setIn4Layers: function (
obj,
keyArray,
value,
splice,
assumeLayerHierarchy
) {
if (keyArray == null || keyArray === []) return false
if (typeof keyArray === 'string') keyArray = keyArray.split('.')
let object = obj
for (let i = 0; i < keyArray.length - 1; i++) {
if (object.hasOwnProperty(keyArray[i])) object = object[keyArray[i]]
else if (
assumeLayerHierarchy &&
Formulae_.objectArrayIndexOfKeyWithValue(
object,
'name',
keyArray[i]
) >= 0
)
object =
object[
Formulae_.objectArrayIndexOfKeyWithValue(
object,
'name',
keyArray[i]
)
]
else return false
}
const finalKey = keyArray[keyArray.length - 1]

if (splice && !isNaN(finalKey) && typeof object.splice === 'function')
object.splice(parseInt(finalKey), 0, value)
else object[keyArray[keyArray.length - 1]] = value
return true
},
traverseLayers: function (layers, onLayer) {
depthTraversal(layers, 0, [])
function depthTraversal(node, depth, path) {
for (var i = 0; i < node.length; i++) {
const ret = onLayer(node[i], path, i)

if (ret === 'remove') {
node.splice(i, 1)
i--
}
//Add other feature information while we're at it
else if (
node[i] &&
node[i].sublayers != null &&
node[i].sublayers.length > 0
) {
depthTraversal(
node[i].sublayers,
depth + 1,
`${path.length > 0 ? path + '.' : ''}${node[i].name}`
)
}
}
}
},
invertGeoJSONLatLngs(feature) {
let geojson = this.clone(feature)
var coords = geojson.geometry.coordinates
Expand Down
12 changes: 10 additions & 2 deletions src/essence/Basics/Layers_/Layers_.js
Original file line number Diff line number Diff line change
Expand Up @@ -2505,7 +2505,7 @@ const L_ = {
},
parseConfig: parseConfig,
// Dynamically add a new layer or update a layer (used by WebSocket)
addNewLayer: async function (data, layerName, type) {
modifyLayer: async function (data, layerName, type) {
// Save so we can make sure we reproduce the same layer settings after parsing the config
const toggledArray = { ...L_.layers.on }

Expand Down Expand Up @@ -2544,14 +2544,22 @@ const L_ = {
}
delete L_.layers.on[layerName]
}

if (ToolController_.activeToolName === 'LayersTool') {
const layersTool = ToolController_.getTool('LayersTool')
if (layersTool.destroy && layersTool.make) {
layersTool.destroy()
layersTool.make()
}
}
},
updateLayersHelper: async function (layerQueueList) {
if (layerQueueList.length > 0) {
while (layerQueueList.length > 0) {
const firstLayer = layerQueueList.shift()
const { data, newLayerName, type } = firstLayer

await L_.addNewLayer(data, newLayerName, type)
await L_.modifyLayer(data, newLayerName, type)
}

if (L_.Map_) L_.Map_.orderedBringToFront(true)
Expand Down
60 changes: 42 additions & 18 deletions src/essence/essence.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,35 +130,45 @@ var essence = {
initialWebSocketRetryInterval: 60000, // 1 minute
webSocketRetryInterval: 60000, // Start with this time and double if disconnected
webSocketPingInterval: null,
connectWebSocket: function(path, initial) {
connectWebSocket: function (path, initial) {
// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState
if (essence.ws === undefined || essence.ws === null
|| (essence.ws && essence.ws.readyState === 3)) {

if (
essence.ws === undefined ||
essence.ws === null ||
(essence.ws && essence.ws.readyState === 3)
) {
essence.initWebSocket(path)

// If we're trying to start the WebSocket for the first time, we know we're not connected already
// so we don't need to retry to connect yet
if (!initial) {
clearInterval(essence.webSocketPingInterval)
essence.webSocketRetryInterval = essence.webSocketRetryInterval * 2
essence.webSocketPingInterval = setInterval(essence.connectWebSocket, essence.webSocketRetryInterval, path, false) // 10 seconds
essence.webSocketRetryInterval =
essence.webSocketRetryInterval * 2
essence.webSocketPingInterval = setInterval(
essence.connectWebSocket,
essence.webSocketRetryInterval,
path,
false
) // 10 seconds
}
}
},
initWebSocket: function(path) {
initWebSocket: function (path) {
essence.ws = new WebSocket(path)

essence.ws.onerror = function(e) {
essence.ws.onerror = function (e) {
console.log(`Unable to connect to WebSocket at ${path}`)

M.Toast.dismissAll()

M.toast({
html: `Not connected to WebSocket. Will retry in ${(essence.webSocketRetryInterval / 60000).toFixed(2)} minutes...`,
html: `Not connected to WebSocket. Will retry in ${(
essence.webSocketRetryInterval / 60000
).toFixed(2)} minutes...`,
displayLength: 10000,
classes: "mmgisToast failure",
});
classes: 'mmgisToast failure',
})
}

essence.ws.onopen = function () {
Expand All @@ -168,16 +178,25 @@ var essence = {

M.Toast.dismissAll()

if (essence.webSocketRetryInterval > essence.initialWebSocketRetryInterval) {
if (
essence.webSocketRetryInterval >
essence.initialWebSocketRetryInterval
) {
M.toast({
html: "Successfully connected to WebSocket",
html: 'Successfully connected to WebSocket',
displayLength: 1600,
classes: "mmgisToast",
});
classes: 'mmgisToast',
})

essence.webSocketRetryInterval = essence.initialWebSocketRetryInterval
essence.webSocketRetryInterval =
essence.initialWebSocketRetryInterval
clearInterval(essence.webSocketPingInterval)
essence.webSocketPingInterval = setInterval(essence.connectWebSocket, essence.webSocketRetryInterval, path, false) // 1 minute
essence.webSocketPingInterval = setInterval(
essence.connectWebSocket,
essence.webSocketRetryInterval,
path,
false
) // 1 minute
}
}

Expand Down Expand Up @@ -360,7 +379,12 @@ var essence = {
: `${protocol}://${window.location.host}/`

essence.connectWebSocket(path, true)
essence.webSocketPingInterval = setInterval(essence.connectWebSocket, essence.webSocketRetryInterval, path, false) // 10 seconds
essence.webSocketPingInterval = setInterval(
essence.connectWebSocket,
essence.webSocketRetryInterval,
path,
false
) // 10 seconds
}
},
swapMission(to) {
Expand Down
Loading

0 comments on commit ed937d7

Please sign in to comment.