Leaftlet: How to dynamically filter a GeoJSON dataset in to a series of Layer checkboxes? - leaflet

I have a web page which fetches some marker pin locations from a local database and also a collection of features from a remote GeoJSON source (API).
Currently, there are two check boxes available to the user to allow them to choose which of the two layers they want to view. This all works fine:
<script>
// Center the map
var map = L.map('map').setView([54.233669, -4.406027], 6);
// Attribution
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=REMOVED', {
attribution: 'Map © OpenStreetMap',
id: 'mapbox.streets'
}).addTo(map);
// Create an empty layergroup for the Locations data
var LayerLocations = L.layerGroup();
// Format the popup markers for the Locations
function forEachFeature(feature, layer) {
// Image
var ImageContent
if (feature.properties.ImageURL) {
ImageContent = "<img src='" + feature.properties.ImageURL + "' width='300' height='200' /><br /><br />"
} else if (feature.properties.YouTubeID) {
ImageContent = "<img src='https://img.youtube.com/vi/" + feature.properties.YouTubeID + "/hqdefault.jpg' width='300' height='200' /><br /><br />"
} else {
ImageContent = ""
}
// Build the popup content
var popupContent = "<h4>" +
feature.properties.Title +
Author +
"</h4>" +
ImageContent +
CommentsContent +
"View and discuss this location.";
layer.bindPopup(popupContent);
}
// Build layer: Locations
fetch("JSONMapPoints.json")
.then(function (response) { return response.json() })
.then(function (data) {
// Create a L.GeoJSON out of the data
var locations = L.geoJson(data, {
onEachFeature: forEachFeature,
pointToLayer: function (feature, latlng) {
return L.marker(latlng, {
icon: L.icon({
iconUrl: "images/pins/" + feature.properties.CategoryID + ".png",
iconSize: [32, 37],
iconAnchor: [10, 32],
popupAnchor: [5, -30]
}),
})
}
});
// Add the L.GeoJSON instance to the empty layergroup
map.fitBounds(locations.getBounds());
LayerLocations.addLayer(locations).addTo(map);
});
// Create an empty layergroup for the Guardian UTM data
var LayerGuardianUTM = L.layerGroup();
// Style the Guardian UTM features
function setStyle(feature) {
return {
fillColor: feature.properties.fillColor,
color: feature.properties.strokeColor,
fillOpacity: feature.properties.fillOpacity,
opacity: feature.properties.strokeOpacity
};
}
// Build Layer: Guardian UTM
function getGuardianUTMdata() {
LayerGuardianUTM.clearLayers();
fetch("https://example.com/v2/mapdata/geojson?n=" + map.getBounds().getNorth() + "&e=" + map.getBounds().getEast() + "&s=" + map.getBounds().getSouth() + "&w=" + map.getBounds().getWest(), { headers: { 'Authorization': 'REMOVED', 'X-AA-DeviceId': 'mySite' } })
.then(function (responseGuardianUTM) { return responseGuardianUTM.json() })
.then(function (dataGuardianUTM) {
// Create a L.GeoJSON out of the data
var featuresAA = L.geoJson(dataGuardianUTM, {
style: setStyle,
pointToLayer: function (feature, latlng) {
return L.marker(latlng, { icon: L.icon({ iconUrl: feature.properties.iconUrl }), })
},
onEachFeature: function (feature, layer) {
layer.bindPopup(feature.properties.name);
},
});
// Add the L.GeoJSON instance to the empty layergroup
LayerGuardianUTM.addLayer(featuresAA).addTo(map);
});
}
// Update the Guardian UTM layer if the map moves
map.on('dragend', function () { getGuardianUTMdata(); });
map.on('zoomend', function () { getGuardianUTMdata(); });
// Layer controls
var layerControl = new L.Control.Layers(null, {
'Locations': LayerLocations,
'Restrictions & Hazards': LayerGuardianUTM
}).addTo(map);
</script>
I wish to increase the functionality available to the end user.
Instead of a single checkbox to turn on/off the Restrictions & Hazards layer, I wish to iterate through the GeoJSON returned from the API and dynamically build a series of checkboxes under the Layer button/icon based on the feature.properties.filters.name.
So when the user clicks on the Layers button icon they should see a series of checkboxes which would allow them to pick and choose which of the features in the GeoJSON they wish to view.
The GeoJSON returned from the API is dynamic and its content changes based on the users location and zoom level.
An example of the GeoJSON is:
{
"isCompleteData": true,
"excludedData": [],
"countriesInViewport": [],
"nationalFlightRestrictions": [],
"features": [
{
"geometry": {
"coordinates": [
[
-2.6300508975982666,
53.536331176757812
],
[
-2.6293964385986328,
53.533683776855469
],
[
-2.6288816928863525,
53.531524658203125
],
[
-2.6228303909301758,
53.529739379882813
],
[
-2.6218380928039551,
53.528053283691406
],
[
-2.6206841468811035,
53.526073455810547
]
],
"type": "LineString"
},
"id": "A05B59534A594F20583A3B8EB479F211E507F265",
"properties": {
"hazardFactor": "40",
"hazardFactorName": "Warning",
"fillColor": "#ffbb00",
"strokeColor": "#b88702",
"fillOpacity": "0.35",
"strokeWidth": "1",
"strokeOpacity": "0.8",
"detailedCategory": "power:line",
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=power_line.png",
"name": "Power Line",
"category": "groundHazard",
"filters": [
{
"name": "Ground Hazards",
"property": "show",
"active": true
}
],
"display": {
"category": "Ground Hazard",
"detailedCategory": "Power Line",
"title": "Ground Hazard",
"sections": [
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=power_line.png",
"title": "Power Hazard",
"text": "The highlighted area is believed to contain power infrastructure. Power infrastructure presents heightened risk of damage to your equipment and critical National infrastructure."
},
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Summary",
"text": "Yellow zones indicate regions where operation of your drone may raise security, privacy or safety concerns."
}
],
"actions": []
}
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
-2.6228303909301758,
53.529739379882813
],
"type": "Point"
},
"id": "6EB24E66D75083A4A135296C12BE004D79629818",
"properties": {
"hazardFactor": "40",
"hazardFactorName": "Warning",
"fillColor": "#ffbb00",
"strokeColor": "#b88702",
"fillOpacity": "0.35",
"strokeWidth": "1",
"strokeOpacity": "0.8",
"detailedCategory": "power:tower",
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=power_tower.png",
"name": "Power Pylon",
"category": "groundHazard",
"filters": [
{
"name": "Ground Hazards",
"property": "show",
"active": true
}
],
"display": {
"category": "Ground Hazard",
"detailedCategory": "Power Pylon",
"title": "Ground Hazard",
"sections": [
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=power_tower.png",
"title": "Power Hazard",
"text": "The highlighted area is believed to contain power infrastructure. Power infrastructure presents heightened risk of damage to your equipment and critical National infrastructure."
},
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Summary",
"text": "Yellow zones indicate regions where operation may raise security, privacy or safety concerns."
}
],
"actions": []
}
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
[
[
-2.6234986782073975,
53.533077239990234
],
[
-2.6215133666992187,
53.528900146484375
],
[
-2.6183879375457764,
53.529270172119141
],
[
-2.6178712844848633,
53.529655456542969
]
]
],
"type": "Polygon"
},
"id": "557952B3668AC5DF5C583BE8E8C1840D97B5ABD4",
"properties": {
"hazardFactor": "40",
"hazardFactorName": "Warning",
"fillColor": "#ffbb00",
"strokeColor": "#b88702",
"fillOpacity": "0.35",
"strokeWidth": "1",
"strokeOpacity": "0.8",
"detailedCategory": "landuse:cemetery",
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=landuse_cemetery.png",
"name": "Wigan Borough Cemetery",
"category": "groundHazard",
"filters": [
{
"name": "Ground Hazards",
"property": "show",
"active": true
}
],
"display": {
"category": "Ground Hazard",
"detailedCategory": "Cemetery",
"title": "Wigan Borough Cemetery",
"sections": [
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Summary",
"text": "Yellow zones indicate regions where operation of your drone may raise security, privacy or safety concerns."
}
],
"actions": []
}
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
[
[
-3.235,
53.53694
],
[
-3.05278,
53.45944
],
[
-3.20139,
53.38583
],
[
-3.02778,
53.24083
],
[
-2.73028,
53.10722
]
]
],
"type": "Polygon"
},
"id": "616CB45B9DA924146E9A5483843B588B36F0AD31",
"properties": {
"hazardFactor": "60",
"hazardFactorName": "Danger",
"fillColor": "#ffffff",
"strokeColor": "#ffffff",
"fillOpacity": "0.2",
"strokeWidth": "1",
"strokeOpacity": "0.8",
"detailedCategory": "type:tma",
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=class_type_tma.png",
"airac": {
"to": "2019-08-15",
"from": "2019-07-18"
},
"altitudeFloor": {
"datum": "Msl",
"meters": 1066.7999983784639,
"feet": 3499.9999946799994
},
"altitudeCeiling": {
"datum": "Sps",
"meters": 7467.5999886492482,
"feet": 24499.99996276
},
"name": "MANCHESTER TMA 1",
"listOrderHint": "1000",
"category": "airspace",
"designator": "EGCC1",
"airspaceType": "TMA",
"filters": [
{
"name": "Upper Airspace",
"property": "show",
"active": false
},
{
"name": "Type TMA",
"property": "show",
"active": true
}
],
"display": {
"category": "airspace",
"detailedCategory": "Type TMA",
"title": "MANCHESTER TMA 1",
"sections": [
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Altitude",
"text": "This piece of airspace is in effect above 1067m / 3500ft MSL"
},
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Terminal control area",
"text": "Control area normally established at the confluence of ATS routes in the vicinity of one or more major aerodromes."
},
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Regulated Airspace",
"text": "This airspace has a specific classification."
},
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Summary",
"text": "Red zones are regulated high-risk areas."
}
],
"actions": []
}
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
[
[
-3.05278,
53.45944
],
[
-2.06667,
53.575
],
[
-2.83333,
53.53333
],
[
-3.05278,
53.45944
]
]
],
"type": "Polygon"
},
"id": "BC69E04789D9A790DB5B29B0EE2804D42E4FA12A",
"properties": {
"hazardFactor": "60",
"hazardFactorName": "Danger",
"fillColor": "#ffffff",
"strokeColor": "#ffffff",
"fillOpacity": "0.2",
"strokeWidth": "1",
"strokeOpacity": "0.8",
"detailedCategory": "class:d",
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=class_d.png",
"airac": {
"to": "2019-08-15",
"from": "2019-07-18"
},
"altitudeFloor": {
"datum": "Msl",
"meters": 761.99999884176,
"feet": 2499.9999961999997
},
"altitudeCeiling": {
"datum": "Msl",
"meters": 1066.7999983784639,
"feet": 3499.9999946799994
},
"name": "MANCHESTER CTA 1",
"listOrderHint": "600",
"category": "airspace",
"designator": "EGCC1",
"airspaceClass": "D",
"airspaceType": "CTA",
"filters": [
{
"name": "Upper Airspace",
"property": "show",
"active": false
},
{
"name": "Class D",
"property": "show",
"active": true
}
],
"display": {
"category": "airspace",
"detailedCategory": "Class D",
"title": "MANCHESTER CTA 1",
"sections": [
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Altitude",
"text": "This piece of airspace is in effect above 762m / 2500ft MSL"
},
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Regulated Airspace",
"text": "This airspace has a specific classification."
},
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Summary",
"text": "Red zones are regulated high-risk areas."
}
],
"actions": []
}
},
"type": "Feature"
},
{
"geometry": {
"coordinates": [
[
[
-10,
54.56667
],
[
-9,
54.75
],
[
-8.25,
55.33333
]
]
],
"type": "Polygon"
},
"id": "11DD2D3CBA8992F29E49A277FC322D19FCD67066",
"properties": {
"hazardFactor": "60",
"hazardFactorName": "Danger",
"fillColor": "#ffffff",
"strokeColor": "#ffffff",
"fillOpacity": "0.2",
"strokeWidth": "1",
"strokeOpacity": "0.8",
"detailedCategory": "type:cta",
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=class_type_cta.png",
"airac": {
"to": "2019-08-15",
"from": "2019-07-18"
},
"altitudeFloor": {
"datum": "Sps",
"meters": 7467.5999886492482,
"feet": 24499.99996276
},
"altitudeCeiling": {
"datum": "Sps",
"meters": 20116.799969422464,
"feet": 65999.99989968
},
"name": "UPPER AIRSPACE CTA",
"listOrderHint": "1000",
"category": "airspace",
"designator": "EGUP",
"airspaceType": "CTA",
"filters": [
{
"name": "Upper Airspace",
"property": "show",
"active": false
},
{
"name": "Type CTA",
"property": "show",
"active": true
}
],
"display": {
"category": "airspace",
"detailedCategory": "Type CTA",
"title": "UPPER AIRSPACE CTA",
"sections": [
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Altitude",
"text": "This piece of airspace is in effect above FL244.9999996276"
},
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Control area",
"text": "A controlled airspace extending upwards from a specified limit above the earth."
},
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Regulated Airspace",
"text": "This airspace has a specific classification."
},
{
"iconUrl": "https://aa-ne-prod-public-api.example.com//v1/map/icon?icon=warning.png",
"title": "Summary",
"text": "Red zones are regulated high-risk areas."
}
],
"actions": []
}
},
"type": "Feature"
}
],
"bbox": [
-2.6261,
53.5288,
-2.6201,
53.5308
],
"type": "FeatureCollection"
}
Based on the example GeoJSON the Layers button should contain checkboxes for:
Locations
Ground Hazards
Upper Airspace
Is this even possible?!

Create more than one L.GeoJSON instance, and leverage their filter option:
A Function that will be used to decide whether to include a feature or not. The default is to include all features:
function (geoJsonFeature) {
return true;
}
Note: dynamically changing the filter option will have effect only on newly added data. It will not re-evaluate already included features.
e.g.:
fetch("JSONMapPoints.json")
.then(function (response) { return response.json() })
.then(function (data) {
var locations = L.geoJson(data, {
filter: function(feat) { return feat.properties.filters.name === 'Location'},
/* etc */
});
var hazards = L.geoJson(data, {
filter: function(feat) { return feat.properties.filters.name === 'Hazard'},
/* etc */
});
var airspace = L.geoJson(data, {
filter: function(feat) { return feat.properties.filters.name === 'Air Space'},
/* etc */
});
});
With those different L.GeoJSON instances, adding them to a layers control is just a matter of calling addOverlay(), e.g.
layersControl.addOverlay(locations, "Locations");
layersControl.addOverlay(hazards, "Hazards);
layersControl.addOverlay(airspace, "Air Space");
Beware of scope, though. A newbie JS mistake is to assume that a variable will magically exist in the scope where it's needed. In this particular case, I would ensure that the layers control is already instantiated when the GeoJSON layers are created, and add them to said layers control.
A more convoluted approach would be to automatically detect the categories or filters, iterating through the GeoJSON structure to get them, e.g.:
fetch("JSONMapPoints.json")
.then(function (response) { return response.json() })
.then(function (data) {
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
var categories = new Set();
for (var i in data) {
var feature = data[i];
categories.add(feature.properties.filters.name);
}
Then iterate through the categories to programmatically create the L.GeoJSON instances, paying attention to the appropriate closures:
categories.forEach(function(category) {
var layer = L.geoJSON(data, {
filter: function(f){ f.properties.filters.name === category }
/* etc */ });
layersControl.addOverlay(layer, category);
});
// And we're done here.
});
This is a bit over-engineered for a dataset needing three filters, but would work nicely when there's a larger amount.

Related

Why does Adaptive Card Input.Date subtract 1 day from the selected date?

Using botframework with msteams channel.
The adaptive card. Nothing special, just a few textblocks and date inputs. The input values are saved to memory.
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.3",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"style": "emphasis",
"width": 2,
"items": [
{
"type": "TextBlock",
"text": "Please enter the information below",
"size": "Large"
},
{
"type": "Container",
"style": "emphasis",
"items": [
{
"type": "TextBlock",
"text": "End Of Last Month",
"wrap": true
},
{
"type": "Input.Date",
"id": "Date_EndOfLastMonth",
"value": "${dialog.requestForm.Date_EndOfLastMonth}",
"placeholder": ""
},
{
"type": "TextBlock",
"text": "${dialog.requestForm.Date_EndOfLastMonthError}",
"size": "small",
"wrap": true,
"color": "attention"
}
]
},
{
"type": "Container",
"style": "emphasis",
"items": [
{
"type": "TextBlock",
"text": "End Of Last Year",
"wrap": true
},
{
"type": "Input.Date",
"id": "Date_EndOfLastYear",
"value": "${dialog.requestForm.Date_EndOfLastYear}",
"placeholder": ""
},
{
"type": "TextBlock",
"text": "${dialog.requestForm.Date_EndOfLastYearError}",
"size": "small",
"wrap": true,
"color": "attention"
}
]
},
{
"type": "Container",
"style": "emphasis",
"items": [
{
"type": "TextBlock",
"text": "Valuation Key Date",
"wrap": true
},
{
"type": "Input.Date",
"id": "ValuationKeyDate",
"value": "${dialog.requestForm.ValuationKeyDate}",
"placeholder": ""
},
{
"type": "TextBlock",
"text": "${dialog.requestForm.ValuationKeyDateError}",
"size": "small",
"wrap": true,
"color": "attention"
}
]
},
{
"type": "Container",
"style": "emphasis",
"items": [
{
"type": "ActionSet",
"actions": [
{
"type": "Action.Submit",
"title": "Submit",
"data": {
"msteams": {
"type": "messageBack",
"displayText": "Form Submitted",
"text": "submitform"
}
}
}
]
}
]
}
]
}
]
}
]
}
The code that goes with the card. I save the input values to memory, so when there is a reprompt happening, I can fill them back to the form. In the last part are the lines for debugging. I am printing out the values directly from the activity, so nothing happens between the submission and the debug.
new CodeAction(async (dc, options) =>
{
dc.State.SetValue("dialog.requestForm.Date_EndOfLastMonth", "");
dc.State.SetValue("dialog.requestForm.Date_EndOfLastYear", "");
dc.State.SetValue("dialog.requestForm.ValuationKeyDate", ""); //ValuationKeyDate
dc.State.SetValue("dialog.requestForm.Date_EndOfLastMonthError", "");
dc.State.SetValue("dialog.requestForm.Date_EndOfLastYearError", "");
dc.State.SetValue("dialog.requestForm.ValuationKeyDateError", "");
return await dc.EndDialogAsync();
}),
new TextInput()
{
Id = "repromptTRT",
Prompt = new ActivityTemplate("${TreasuryCase()}"),
AllowInterruptions = true,
MaxTurnCount = "if(turn.activity.text != 'submitform', '0', '100')",
},
// deal with interrupt, cancel everything else
new IfCondition()
{
Condition = "turn.activity.text != 'submitform'",
Actions = new List<Dialog>()
{
new EndDialog()
}
},
// save form
new CodeAction(async (dc, options) =>
{
var json = JsonConvert.SerializeObject(dc.Context.Activity.Value);
await dc.Context.SendActivityAsync(MessageFactory.Text(json));
var d = JsonConvert.SerializeObject(DateTime.Now);
await dc.Context.SendActivityAsync(MessageFactory.Text(d));
var o = JsonConvert.DeserializeObject<TC>(json);
dc.State.SetValue("dialog.requestForm", o);
return await dc.EndDialogAsync();
}),
For more visibility, adding answer here from comment section:
The fix should be rolling through the rings, so the change should manifest in public clients in a couple of weeks.

Google smart home action always failing on command execution

I am developing fanSpeed and colorSetting trait for Google Actions, but whenever I execute command for setting fan speed or setting light colour, it fails. Here is my JSON for sync,query and execute step:
sync
{
"requestId":"d25ty67q-98jui-581aa-j891-b1f6dhuas"
"payload":{
"devices": [
{
"id": "AA96A0#16",
"type": "action.devices.types.FAN",
"traits": [
"action.devices.traits.OnOff",
"action.devices.traits.FanSpeed"
],
"roomHint": "ROOM",
"deviceInfo": {
"manufacturer": "smart Homes"
},
"name": {
"defaultNames": [
"fan"
],
"name": "fan"
},
"willReportState": true,
"customData": {
},
"attributes": {
"commandOnlyOnOff": false,
"commandOnlyFanSpeed": false,
"reversible": false,
"availableFanSpeeds": {
"speeds": [
{"speed_name": "speed_low", "speed_values": [{"speed_synonym": ["low", "low speed", "slow"], "lang": "en"}]},
{"speed_name": "speed_medium", "speed_values": [{"speed_synonym": ["medium", "medium speed", "med"], "lang": "en"}]},
{"speed_name": "speed_high", "speed_values": [{"speed_synonym": [ "high speed", "high"], "lang": "en"}]},
{"speed_name": "speed_highest", "speed_values": [{"speed_synonym": [ "highest speed", "highest"], "lang": "en"}]}
],
"ordered": true
}
}
},
{
"id": "240A50#0",
"type": "action.devices.types.LIGHT",
"traits": [
"action.devices.traits.OnOff",
"action.devices.traits.ColorSetting"
],
"roomHint": "ROOM",
"deviceInfo": {
"manufacturer": "smart Homes"
},
"name": {
"defaultNames": [
"light"
],
"name": "light"
},
"willReportState": false,
"customData": {
},
"attributes": {
"commandOnlyColorSetting": true,
"colorModel": "hsv"
}
}
],
"agentUserId": "Home555"
}
}
Execute Fan:
{
"commands": [
{
"ids": [
"AA96A0#16"
],
"status": "SUCCESS",
"states": {
"online": true,
"on": true,
"currentFanSpeedSetting": "speed_highest"
}
}
]
}
Query Fan:
{
"requestId":"167278043664013971",
"payload":{
"devices": {
"AA96A0#16": {
"status": "SUCCESS",
"online": true,
"on": true,
"currentFanSpeedSetting": "speed_highest"
}
}
}
}
Execute color-light:
{
"commands": [
{
"ids": [
"240A50#0"
],
"status": "SUCCESS",
"states": {
"online": true,
"on": true,
"color": {
"spectrumHSV": {
"hue": 120,
"saturation": 1,
"value": 1
}
}
}
}
]
}
Query color-light:
{
"requestId":"167278043664013971",
"payload":{
"devices": {
"240A50#0": {
"status": "SUCCESS",
"online": true,
"on": true,
"color": {
"spectrumHSV": {
"hue": 0,
"saturation": 0,
"value": 1
}
}
}
}
}
}
Speaker response: Sorry, it looks like smart homes is unavailable right now.
However my request is always executing and I can see state change on my device. Can anyone point out Why it is failing?
The json for execute for both the fan and the bulb has missing values for requestId. This might be causing the failures with the execution commands.

Filter for one attribute (array) for one of its value (json)

Having the following record
{
"name": "
 Festões Plástico, 12mt x 17cm - Festas Populares",
"categories": [
"Festas",
"Casamentos",
"Decorações"
],
"hierarchicalCategories": {
"lvl0": "Festas",
"lvl1": "Festas > Casamentos",
"lvl2": "Festas > Casamentos > Decorações"
},
"description": "",
"brand": "Misterius",
"price": 14.94,
"stock": "Disponível",
"prices": [
{
"value": 12,
"type": "specificValue",
"family": "fatos",
"subfamily": "example"
},
{
"value": 13,
"type": "specificValue13",
"family": "fatos13",
"subfamily": "example13"
},
{
"value": 14,
"type": "specificValue14",
"family": "fatos14",
"subfamily": "example14"
},
{
"value": 15,
"type": "specificValue15",
"family": "fatos15",
"subfamily": "example15"
},
{
"value": 16,
"type": "specificValue16",
"family": "fatos16",
"subfamily": "example16"
}
],
"color": [
{
"name": "Amarelo",
"label": "Amarelo,#FFFF00",
"hexa": "#FFFF00"
},
{
"name": "Azul",
"label": "Azul,#0000FF",
"hexa": "#0000FF"
},
{
"name": "Branco",
"label": "Branco,#FFFFFF",
"hexa": "#FFFFFF"
},
{
"name": "Laranja",
"label": "Laranja,#FFA500",
"hexa": "#FFA500"
},
{
"name": "Verde Escuro",
"label": "Verde Escuro,#006400",
"hexa": "#006400"
},
{
"name": "Vermelho",
"label": "Vermelho,#FF0000",
"hexa": "#FF0000"
}
],
"specialcategorie": "",
"reference": "3546",
"rating": 0,
"free_shipping": false,
"popularity": 0,
"objectID": "30"
}
Now by searching for "Festas Populares" will return the record and its attributes, is it possible to also filter for one attribute array as "prices" to only return one json. for example "prices.type"="specificValue14" and "family"="fatos14" and "family"="fatos" and "subfamily"="example"
{
“value”: 14,
“type”: “specificValue14”,
“family”: “fatos14”,
“subfamily”: “example14”
}
the record return would be:
{
"name": "
 Festões Plástico, 12mt x 17cm - Festas Populares",
"categories": [
"Festas",
"Casamentos",
"Decorações"
],
"hierarchicalCategories": {
"lvl0": "Festas",
"lvl1": "Festas > Casamentos",
"lvl2": "Festas > Casamentos > Decorações"
},
"description": "",
"brand": "Misterius",
"price": 14.94,
"stock": "Disponível",
"prices": [
{
"value": 14,
"type": "specificValue14",
"family": "fatos14",
"subfamily": "example14"
}
],
"color": [
{
"name": "Amarelo",
"label": "Amarelo,#FFFF00",
"hexa": "#FFFF00"
},
{
"name": "Azul",
"label": "Azul,#0000FF",
"hexa": "#0000FF"
},
{
"name": "Branco",
"label": "Branco,#FFFFFF",
"hexa": "#FFFFFF"
},
{
"name": "Laranja",
"label": "Laranja,#FFA500",
"hexa": "#FFA500"
},
{
"name": "Verde Escuro",
"label": "Verde Escuro,#006400",
"hexa": "#006400"
},
{
"name": "Vermelho",
"label": "Vermelho,#FF0000",
"hexa": "#FF0000"
}
],
"specialcategorie": "",
"reference": "3546",
"rating": 0,
"free_shipping": false,
"popularity": 0,
"objectID": "30"
}
for some context a product can have multiple prices associated, for a specific user, or one day there is campaign giving discount, etc so for that cases want to filter price associated to the product/record.
No, this is not possible with Algolia. Records are always returned with the attributes specified inside attributesToRetrieve. These attributes are returned in full.

random document aggregate by two or more fields

I have questions list and they have structure like this:
{
"_id": {
"$oid": "5a077c418fdf294df73bf7ea"
},
"author": "PinkyaRabbit",
"question": "Что здесь литерал: var x = {};",
"category": "Javascript шаблоны",
"answers": [
"var x",
"{}",
"var x = {};",
"Нету тут его"
],
"real": "{}",
"description": "Литерал, это нейкое чистое значение, которое встречается в коде. Цифра, слово, объект - что угодно",
"users": [
{
"status": "good",
"wasToday": true,
"user": 471317129
}
],
"blockedBy": [],
"date": "2017-11-12T01:40:01+03:00"
}
I need to pick one random, question for user. Each user have personal categories list. blockedBy its an array if user dont want to take. I pick personal categories list like array user.categories and try to make aggregate request.
TGquestions.aggregate([
{ $match: {
users:{
"status": "clear",
"wasToday": false,
"user": chatid
},
category: { $in: user.categories },
blockedBy: { $ne: chatid}
}},
{ $sample: { size: 1 } }
], (err, qq) => {
The problem is that looks like my random not working atall. For example, first picked question from category with name "Javascript", next will be same from "Javascript", and white question not ends all next will be from this category. When question in this category ends, with next category same problem. The worst in this is that queue of categories each time same. So my random aggregate totaly failed. How to fix this?
upd. example
I have a user
{
"_id": {
"$oid": "5a15aa31457bca063c01248b"
},
"chatid": 213229659,
"username": "iamRB01",
"baned": false,
"name": "Rina",
"categories": ["Javascript", "Java"],
}
And some questions from some categories. I changed _id's to make all more readable.
{
"_id": "1",
"author": "PinkyaRabbit",
"question": "Что здесь литерал: var x = {};",
"category": "Javascript",
"answers": [
"var x",
"{}",
"var x = {};",
"Нету тут его"
],
"real": "{}",
"description": "Литерал, это нейкое чистое значение, которое встречается в коде. Цифра, слово, объект - что угодно",
"users": [
{
"status": "clear",
"wasToday": false,
"user": 213229659
}
],
"blockedBy": [],
"date": "2017-11-12T01:40:01+03:00"
},
{
"_id": "2",
"author": "PinkyaRabbit",
"question": "Первым признаком конструктора является?",
"category": "Javascript",
"answers": [
"слово new",
"метод construct",
"имя пишется с большой буквы",
"возможно обращение через this"
],
"real": "имя пишется с большой буквы",
"description": "Если имя чего-то пишется с большой буквы в Javascript, то это конструктор (если код писал не криворукий рак)",
"users": [
{
"status": "clear",
"wasToday": false,
"user": 213229659
}
],
"blockedBy": [],
"date": "2017-11-13T16:20:50+03:00"
},
{
"_id": "3",
"author": "Xiroho",
"question": "Для записи пятеричной системы счисления используются цифры -",
"category": "Java",
"answers": [
"01234",
"0123456789",
"01",
"10"
],
"real": "01234",
"description": "",
"users": [
{
"status": "clear",
"wasToday": false,
"user": 213229659
}
],
"date": "2017-11-20T23:31:03+03:00",
"blockedBy": []
},
{
"_id": "4",
"author": "PinkyaRabbit",
"question": "Как проверить, есть ли элемент в массиве",
"category": "Javascript",
"answers": [
"сделать цикл с проверкой if(haystack[i]===needle){return true;}",
"использовать проверку через filter",
"использовать функцию indexOf",
"использовать функцию includes"
],
"real": "использовать функцию includes",
"users": [
{
"status": "clear",
"wasToday": false,
"user": 213229659
}
],
"description": "Хотя indexOf работает верно\nif(array.indexOf(\"test\") > -1){ result++; }\nона проигрывает includes по быстродействию\narray.includes(\"test\")\nпоэтому стоит использовать includes. Тем не менее, не все браузеры поддерживают includes, которая появилась только в 2016 году, поэтому indexOf знать тоже надо",
"blockedBy": [213229659],
"date": "2017-12-01T01:21:09+03:00"
}
I want to pick one random question of this collection. For example first id=2, next with id 3, next with id 1 but not with id 4 cuz user turned off this question. But in my script looks like aggregate random not working... =(
The answer was that in nested arrays for picking real results needed to use $elemMatch.
TGquestions.aggregate([
{ $match: {
category: { $in: user.categories },
users: {
$elemMatch: {
"status": "clear",
"wasToday": false,
"user": chatid
}},
blockedBy: { $ne: chatid}
}},
{ $sample: { size: 1 } }
], (err, qq) => {

Gupshup consume post to create Embedding form

im working with Gupshup and I want to add subview in my chat.
To make this I create this vars:
var url="https://api.gupshup.io/sm/api/facebook/smartmsg/form/create";
var header = {"apikey":"xxxxxxxxxxxxxxxxxxx","Content-Type": "application/x-www-form-urlencoded","Accept":"application/json"};
var param={"formJSON":{
"title": "This is a test.",
"autoClose": false,
"message": "Thank You",
"callback-url": "https://www.gupshup.io/developer/bot/Cotizador/public",
"fields": [{
"type": "fbid",
"name": "fbname",
"label": "fbName"
}, {
"type": "input",
"name": "name",
"label": "Name",
"validations": [{
"regex": "^[A-Z a-z]+$",
"msg": "Only alphabets are allowed in this field"
}, {
"regex": "^[A-Z a-z]{6,}$",
"msg": "Minimum 6 characters required"
}]
}, {
"type": "radio",
"name": "gender",
"label": "Gender",
"options": [
"Male",
"Female"
],
"validations": [{
"regex": "",
"msg": ""
}]
}, {
"type": "select",
"name": "account",
"label": "AccountType",
"options": [
"current",
"savings"
],
"validations": [{
"regex": "",
"msg": ""
}]
}, {
"type": "checkbox",
"name": "interest",
"label": "Interests",
"options": [
"Cooking",
"Reading"
],
"validations": [{
"regex": "",
"msg": ""
}]
}],
"users": [
"Testing"
]
}}
And call post with:
context.simplehttp.makePost(url,JSON.stringify(param),header,parser);
And my call back
function parser(context, event) {
context.console.log("Handler https")
var result= JSON.parse(event.getresp);
if(result=="success"){
context.sendResponse("We have successfully stored your data");
}else{
context.sendResponse("We dont shoot");
}
}
But when, make the request post, but don't show me response in chat or in callback. What im doing wrong?
Sohan from Gupshup here.
The result from the API that you are using is this:
[{
"embedlink": "https://api.gupshup.io/sm/api/facebook/smartmsg/embed/66438dde-ec76-4d6e-a0d0-8cfc0c730e57",
"expired": false,
"fb-button": {
"title": "This is a test.",
"type": "web_url",
"url": "https://api.gupshup.io/sm/api/facebook/smartmsg/embed/66438dde-ec76-4d6e-a0d0-8cfc0c730e57",
"webview_height_ratio": "tall"
},
"id": "66438dde-ec76-4d6e-a0d0-8cfc0c730e57",
"signed-for": {
"display": "Testing",
"subdisplay": "Testing"
},
"smid": "1009"
}]
Thus when you do:
var result= JSON.parse(event.getresp);
if(result=="success"){
context.sendResponse(result) will display the entire JSON that you see above. To display the 'expired' field you can use result.expired.
Check this document for more information.