I am currently using MapBox GL JS in my React project.
I want to know how to display some text at specific coordinates after an interaction is over. I get the coordinates of the location from a function getMinCoords()
My current code is as follows
stop() {
let nextState = this.state;
if (nextState !== 'Complete') {
nextState = 'Stop';
}
if (this.vertices.length > 2) {
this.annotation.geometries = [{
type: 'Polygon',
coordinates: this.GetPolygonCoordinates(),
properties: {
annotationType: 'polygon',
},
}];
} else {
this.annotation.geometries = [];
}
console.log(this.getMinCoords());
this.map.off('click', this.onClickCallback);
this.setState(nextState);
this.flushState();
}
The function which takes care of annotations is as follows:
this.geometries.forEach((geometry) => {
switch (geometry.type) {
case 'Point':
case 'LineString':
case 'Polygon': {
const styling = GetStyles(geometry, styles);
const feature = GetFeatureFromGeometry(geometry, this.properties);
if (this.properties.annotationType === 'label') {
styling.textAnchor = 'center';
}
feature.properties = { ...feature.properties, ...styleProps };
features.push(feature);
break;
}
Before the state is flushed, I want to display the area at the coordinates returned by getMinCoords. I have another function getArea(), which returns the area. I just need to display it on the map
You can create an empty Layer once your map is instantiated with empty source like below:
Code Snippet:
//Empty Source
const textGeoJsonSource = {
type: 'geojson',
data: featureCollection //Initially this is empty
};
//Text Layer with textGeoJsonSource
const sectionTextLayer: mapboxgl.Layer = {
"id": "sectionTextLayer",
"type": "symbol",
"source": textGeoJsonSource,
"paint": {
"text-color": textColor, //Color of your choice
"text-halo-blur": textHaloBlur,
"text-halo-color": textHaloColor,
"text-halo-width": textHaloWidth,
"text-opacity": textOpacity
},
"layout": {
"text-field": ['get', 't'], //This will get "t" property from your geojson
"text-font": textFontFamily,
"text-rotation-alignment": "auto",
"text-allow-overlap": true,
"text-anchor": "top"
}
};
this.mapInstance.addLayer(sectionTextLayer);
Then later on set the source of "textGeoJsonSource" to below once you have the coordinates( like getMinCoords in your case ):
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [ //Pass your coordinates here
5.823,
-6.67
]
},
"properties": {
"t": "18C" //Text you want to display on Map
}
}
]
}
Please note few things:
Your feature collection will need point coordinates in order to show text on Map on that point. If you have a polygon, first get centroid of that polygon for that you can use turf:
http://turfjs.org/docs/#centroid
Helpful Links: Centering text label in mapbox-gl-js?
Related
I have a linestring and a polygon and I am using turf.booleanIntersect() to determine if the line goes through the polygon. The example that i have tested and works is:
var poly1 = turf.polygon([
[
[148.535693, -29.6],
[154.553967, -29.64038],
[154.526554, -33.820031],
[148.535693, -33.6],
[148.535693, -29.6]
]
]);
//const p1 = L.geoJSON(poly1).addTo(mymap);
console.log("TEST: " + turf.booleanIntersects(line, poly1));
In my real code I read the polygon values from a file and need to insert them into an array which needs to be converted into a "GeoJSON Feature or Geometry" (from webpage).
I am having trouble getting the array to json convert correct.
var polygonlines = [];
var start = [long,lat];
polygonlines.push([start]); //add multiple of these points to the to polygonlines array
//create my json
var geojsonPolygon =
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": polygonlines
}
}
var turfpolygon = turf.polygon(geojsonPolygon.data.geometry.coordinates); //ERROR HERE
const p2 = L.geoJSON(turfpolygon).addTo(mymap);
var result = turf.booleanIntersects(line, turfpolygon)
The error I get is "Uncaught Error Error: Each LinearRing of a Polygon must have 4 or more Positions."
I can't quite get the structure of the geojsonPolygon correct. I think that it is look at geojsonPolygon Array(1) in attached picture instead of Array(10), but I can't work out how to fix it.
Would love some help getting this structure fixed. Thank you :)
p.s. please ignore values of lat/longs, just examples.
I have seen this question but it hasn't helped How to feed JSON data of coordinates to turf.polygon?
Ongoing answer..., content and code will be edited.
Here is a LIVE demo code that you can run to see how it works. It may help to answer you question.
Usage:
click Run code snippet button
click Full page in top-right corner to see map and console
//Using turf_polygon object style
var poly1 = turf.polygon([
[
[148.535693, -29.6],
[154.553967, -29.64038],
[154.526554, -33.820031],
[148.535693, -33.6],
[148.535693, -29.6]
]
]);
// Using geojson style data
// Coordinates are slightly different from poly1
// This can be used as a good example to compare/contrast with your implementation
// This geojson of poly2 works, you can see it on the map.
var poly2 = {
type: "Feature",
properties: { id: 102, name: "Poly_2" },
geometry: {
type: "Polygon",
coordinates: [
[
[148, -29],
[154, -29],
[154, -33],
[148, -33],
[148, -29]
]
]
}
};
var line12 = turf.lineString([[144, -30], [153, -31.8], [159, -32]]);
var line34 = turf.lineString([[144, -20], [160, -30]]);
/* Init and draw Leaflet map */
var map;
function initMap(coords, zoom) {
// initialize map container
map = L.map("mapid").setView(coords, zoom);
// get the stamen toner-lite tiles
var Stamen_Toner = L.tileLayer(
"https://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.{ext}",
{
attribution:
'Map tiles by Stamen Design, CC BY 3.0 — Map data © OpenStreetMap',
subdomains: "abcd",
minZoom: 0,
maxZoom: 20,
ext: "png"
}
);
// add the tiles to the map
Stamen_Toner.addTo(map);
//disable scroll wheel zoom
map.scrollWheelZoom.disable();
}
/* Leaflet use (lat,long) */
var coordinates = [-30, 150]; //[lat,long]
var zoom = 5;
initMap(coordinates, zoom);
//Add polygons and lines
L.geoJson(turf.featureCollection([poly1, poly2, line12, line34])).addTo(map);
// Intersection op
var result1 = turf.booleanIntersects(line12, poly1); //True
var result2 = turf.booleanIntersects(line34, poly1); //False
console.log(result1, result2);
//EOF
#mapid { height: 480px; width: 800px}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.4/dist/leaflet.css" integrity="sha512-puBpdR0798OZvTTbP4A8Ix/l+A4dHDD0DGqYW6RQ+9jxkRFclaxxQb/SJAWZfWAkuyeQUytO7+7N4QKrDh+drA==" crossorigin="" />
<!-- Make sure you put this AFTER Leaflet's CSS -->
<script src="https://unpkg.com/leaflet#1.3.4/dist/leaflet.js" integrity="sha512-nMMmRyTVoLYqjP9hrbed9S+FzjZHW5gY1TWCHA5ckwXZBadntCNs8kEqAWdrb9O7rxbCaA4lKTIWjDXZxflOcA==" crossorigin=""></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Turf.js/6.5.0/turf.min.js"></script>
<div id="mapid"></div>
How to get the polygonline array into geojson for use in turf polygon:
var polygonlines = [];
//these 2 lines occur multiple times in a loop
{
var start = [vol.lines[k].start.lng, vol.lines[k].start.lat];
polygonlines.push(start);
}
var geojsonPolygon =
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [polygonlines]
}
var turfpolygon = turf.polygon(geojsonPolygon.geometry.coordinates);
turf.booleanIntersects(line, turfpolygon)
Doing some smart home skill development and my Intent utterance is "how long until my battery is {state}" State being either empty or full. I know I could do this with different intents but I wont want to have two many intents as I already have many. I basically want the user to say either full or empty in place of {state}. and then from there, depending on their answer, give different answers for full or empty. I haven't found much online so i hope you guys can help. Im also new to code.
You should create a slot for your battery state:
{
"name": "BatteryState",
"values": [
{
"id": "empty",
"name": {
"value": "empty"
}
},
{
"id": "full",
"name": {
"value": "full"
}
}
]
}
You can create it by yourself in the UI or paste in types collection in JSON editor.
Then use created slot in your intent
{
"name": "BatteryStateIntent",
"slots": [
{
"name": "batteryState",
"type": "BatteryState"
}
],
"samples": [
"how long until my battery is {batteryState}"
]
}
You can create it by yourself in the UI or paste in intents collection in JSON editor.
Then in the code:
const BatteryStateIntentHandler = {
canHandle(handlerInput) {
return (
Alexa.getRequestType(handlerInput.requestEnvelope) === "IntentRequest" &&
Alexa.getIntentName(handlerInput.requestEnvelope) === "BatteryStateIntent"
);
},
handle(handlerInput) {
const batteryStatus =
handlerInput.requestEnvelope.request.intent.slots.batteryState.value;
let speakOutput = "";
if (batteryStatus === "full") {
const timeToFull = 10; // or some sophisticated calculations ;)
speakOutput += `Your battery will be full in ${timeToFull} minutes!`;
} else if (batteryStatus === "empty") {
const timeToEmpty = 5; // or some sophisticated calculations ;)
speakOutput += `Your battery will be full in ${timeToEmpty} minutes!`;
} else {
speakOutput += "I don't know this status";
}
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
},
};
Make sure you used the same intent name as in the builder. And at the end, add created intent handler to request handlers:
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
BatteryStateIntentHandler,
// ...
)
.lambda();
I am building an order functionality of my modules in the component state on react
so the state object looks like that
"activity": {
"name": "rewwerwer",
"description": "werwerwerwerwer",
"modules": [
{
"name": "Text",
"order": 1,
"module_id": 1612,
},
{
"name": "Text2",
"order" 2,
"module_id": 1592,
}
]
}
handleSortUp = (moduleid ,newOrder) => {
const { modules } = this.state.activity;
const module = modules.find(element => element.module_id === moduleid);//Thios returns the correct object
this.setState({ activity: { ...this.state.activity.modules.find(element => element.module_id === moduleid), order: newOrder } });
}
I tried this but it updates the order field and object
but also removes all other objects from modules array :<
I like just to replace only the order field on each module by module id
and leave rest data there
the required response from the state that i need when the handleSortUp(1612,14); is fired
handleSortUp(1612,2);
{
"name": "rewwerwer",
"description": "werwerwerwerwer",
"modules": [
{
"name": "Text",
"order": 2,
"module_id": 1612,
},
{
"name": "Text2",
"order": 1,
"module_id": 1592,
}
]
}
I can do this on a simple array the question is how to update the State on react
Also the one way to change the order is answered fine but how also to change the field that had that order registered
So when we fire Change Item 1 order to 2 the Item 2 needs to take the Order 1
Thank you
Sure! This would be a great place to use the built-in .map method available for arrays. Consider the following example.
let array = [{order: 1, type: "food"}, {order: 2, type: "notfood"} ]
const newArray = array.map((item) => {
//CHECK TO SEE IF THIS IS THE ITEM WE WANT TO UPDATE
if(item.order == 1){
return {
...item,
newField: "newData"
}
} else {
return item
}
})
Output is:
[{order: 1, type: "food", newField: "newData"}
{order: 2, type: "notfood"}]
So yes you could totally update the module you're looking for without mutating the rest of your array. Then use your findings to update the component state using some good ol spread.
this.setState({
activity: {
...this.state.activity,
modules: newArray}
})
Of course they get all eliminated. Pay attention to what you wrote here:
this.setState({ activity: { ...this.state.activity.modules.find(element => element.module_id === moduleid), order: newOrder } });
What are you doing with that find? Let's see what Array.prototype.find() returns: https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Array/find
It returns an index, why would you insert an index into the state?
The answer partially came from yourfavoritedev.
As he said you can use the built-in Array.prototype.map() and do it like this:
handleSortUp = (moduleid ,newOrder) => {
const { modules } = this.state.activity;
const newModules = modules.map(module => module.module_id === moduleid ? { ...module, order: newOrder } : module)
this.setState({ activity: { ...this.state.activity, modules: newModules } });
}
This should work, let me know but I strongly advice to ask me or search on the web if you don't understand what is happening there (syntactically or semantically speaking).
We have a requirement where-in we have to provide drag drop facility for markers / points. The example https://docs.mapbox.com/mapbox-gl-js/example/drag-a-point/ works perfect for 1 marker. Because it is hardcoded to geojson.features[0].geometry.coordinates = [coords.lng, coords.lat];
However, in a multi point scenario how to set the geojson for respective feature which was dragged?
Kindly let know if any further details required.
Thankx
You can achieve that by storing the current marker and applying the change on it during onMove.
I have added a property to each object to define an id:
var geojson = {
type: "FeatureCollection",
features: [
{
type: "Feature",
geometry: {
type: "Point",
coordinates: [0, 0]
},
properties: {
id: 1
}
},
{
type: "Feature",
geometry: {
type: "Point",
coordinates: [0, 1]
},
properties: {
id: 2
}
}
]
};
On the onmousedown event on point layer, store the current feature id
Here features[0] is usable because the event fired on point so the first feature is ever the clicked one
map.on("mousedown", "point", function(e) {
currentFeatureId = e.features[0].properties.id;
// Prevent the default map drag behavior.
e.preventDefault();
canvas.style.cursor = "grab";
map.on("mousemove", onMove);
map.once("mouseup", onUp);
});
After that, in the onMove method you can use it to move the right feature :
function onMove(e) {
var coords = e.lngLat;
// Set a UI indicator for dragging.
canvas.style.cursor = "grabbing";
// Update the Point feature in `geojson` coordinates
// and call setData to the source layer `point` on it.
var currentMarker = geojson.features.find(obj => {
return obj.properties.id === currentFeatureId
})
currentMarker.geometry.coordinates = [coords.lng, coords.lat];
map.getSource("point").setData(geojson);
}
See working exemple here : https://codepen.io/tmacpolo/pen/moxmpZ
I have a JSON text (posted below) and I want to extract name and channel_remote_number from each of its object inside its item array. Can someone please guide how can I do that?
JSON Text:
{
"xml":{
"version":"3.0.0",
"item_startidx":"0",
"total_items":"471",
"items_link":"https://example_url/",
"items":{
"item":[
{
"id":"36438",
"name":"A plus",
"type":"liveWMV",
"link":"https://example_url/",
"duration":"35000",
"channel_logo":{
"#cdata":"http://example_url/"
},
"channel_remote_number":"180",
"description":"A plus",
"response_link":"https://example_url/",
"restrict_link":"https://example_url/",
"play_time":"https://example_url/",
"protected":"no",
"program_listing":"https://example_url/",
"program_guide":"https://example_url/",
"electronic_program_guide":"https://example_url/",
"catchup_tv":"7",
"popup":{
"type":"blocking",
"message":"Temporary Down",
"buttons":{
"button":{
"type":"cancel",
"text":"OK"
}
}
},
"category_id":"12797",
"path":"Smart TV App>Live TV>Pakistani>Entertainment"
},
{
"id":"37669",
"name":"A plus",
"type":"liveWMV",
"link":"https://example_url/",
"duration":"35000",
"channel_logo":{
"#cdata":"http://example_url/"
},
"channel_remote_number":"180",
"description":"A plus",
"response_link":"https://example_url/",
"restrict_link":"https://example_url/",
"play_time":"https://example_url/",
"protected":"no",
"program_listing":"https://example_url/",
"program_guide":"https://example_url/",
"electronic_program_guide":"https://example_url/",
"catchup_tv":"7",
"popup":{
"type":"blocking",
"message":"Temporary Down",
"buttons":{
"button":{
"type":"cancel",
"text":"OK"
}
}
},
"category_id":"12797",
"path":"Smart TV App>Live TV>Pakistani>Entertainment"
}
]
}
}
}
var data = {
"xml": {....
var answer = data.xml.items.item
.map(x => ({ name: x.name, channel_remote_number: x.channel_remote_number }));