I am trying to implement a Geojson layer on a map in React-Native.
Development environment:
react-native-maps: 0.24.2,
expo: 33.0.0
I have tried the following three methods without success:
Overlay
Polygon and icon (using image)
Geojson
I feel Geojson is the simplest and direct method of implementing this layer on a map (Apple Maps for iOS and Google Maps for Android). Geojson method is unfortunately not working.
I don't know how to create a codesandbox for React Native but you will find my code snippet below.
displayLightPollutionLayer() {
const features = {
"type": "FeatureCollection",
"name": "artificialNightSkyBrightness_example",
"crs": {
"type": "name",
"properties":
{
"name": "urn:ogc:def:crs:OGC:1.3:CRS84"
}
},
"features": [
{
"type": "Feature",
"properties":
{
"Name": null,
"description": null,
"drawOrder": 15,
"icon": "https:\/\/nightskybrightness.s3.eu-west-3.amazonaws.com\/ArtificialSkyBrightness537.JPG"
},
"geometry":
{
"type": "Polygon",
"coordinates": [
[
[4.2499263, 50.937513844500003],
[4.2499263, 42.404183924500003],
[-4.12507035, 42.404183924500003],
[-4.12507035, 50.937513844500003],
[4.2499263, 50.937513844500003]
]
]
}
}
]
}
return (
<Geojson geojson={features}/>
)
}
Error:
Invariant Violation: Invariant Violation: Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of LightPollutionAtlas.
Expected result:
The images should be positioned all over the map at predefined coordinates and should be zoomable.
Geojson is a component of 'react-native-geojson' module. So you need to import that module, add this line on top of your class.
import Geojson from 'react-native-geojson';
Also "if haven't already", run npm install react-native-geojson, Inside your project folder.
Also as I have noticed (maybe I am wrong) Geojson doesn't support Images directly so, one thing that you can try is to add this code in return of displayLightPollutionLayer function:
return (
<Geojson geojson={features}>
<Image source="https:\/\/nightskybrightness.s3.eu-west-3.amazonaws.com\/ArtificialSkyBrightness537.JPG" style = {{flex:1}}/>
</Geojson>
)
Here is how I solved my problem. As of today in react-native-maps v0.24.2, Geojson doesn't render images. As per my understanding, Geojson component in react-native-maps renders only points, polygons and polylines. I thus switched to Overlay component to position images at predefined coordinates on the map (Apple Maps for iOS). I haven't tested the solution yet for Google Maps on Android but I believe it should work fine.
I have separated the code into two components :
1. Creation of Overlays.
2. Creation of Map View that incorporates the above overlays.
class PollutionOverlay extends React.Component {
constructor(props) {
super(props)
}
render() {
return(
<View>
<Overlay
image={{ uri: 'valid URI'}}
bounds={[[50.9375138445,-4.12507035],[42.4041839245,4.2499263]]}
/>
<Overlay
image={{ uri: 'valid URI`enter code here`'}}
bounds={[[50.9375138445,4.2499263],[42.4041839245,12.62492295]]}
/>
</View>
)
}
}
export default PollutionOverlay;
-------------------------------------
import PollutionOverlay from 'valid path';
class LightPollutionAtlas extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<MapView
style={styles.mapStyle}
maxZoomLevel={9}
>
<PollutionOverlay />
</MapView>
)
}
}
const styles = StyleSheet.create({
mapStyle: {
flex: 1
}
});
export default LightPollutionAtlas;
Update your displayLightPollutionLayer function as follows, to draw the polygon,
displayLightPollutionLayer(markers) {
const features = {
"type": "FeatureCollection",
"name": "artificialNightSkyBrightness_example",
"crs": { "type": "name", "properties": { "name":
"urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{
"type": "Feature",
"properties": {
"Name": null,
"description": null,
"drawOrder": 15,
"icon": "https:\/\/nightskybrightness.s3.eu-west- 3.amazonaws.com\/ArtificialSkyBrightness537.JPG"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[ 4.2499263, 50.937513844500003 ],
[ 4.2499263, 42.404183924500003 ],
[ -4.12507035, 42.404183924500003 ],
[ -4.12507035, 50.937513844500003 ],
[ 4.2499263, 50.937513844500003 ]
]
]
}
}
]
}
return (<Polygon
coordinates={this.getCoordinates(features)}
title={marker.title}
description={marker.description}
/>);
}
getCoordinates(features) {
let updatedFeatures = features.features[0].geometry.coordinates.map((coordinate) => {latitude: coordinate[0], longitude: coordinate[1]});
return updatedFeatures;
}
render() {
return (
<MapView
region={this.state.region}
onRegionChange={this.onRegionChange}
>
{this.displayLightPollutionPolygonLayer()}
</MapView>
)
}
I have updated the logic, please add all necessary validations to avoid unwanted crashes.
Related
I'm trying to create a deep link into my app but it's not working with https url, it's working with a regular deep link in this format: myapp:// ..., here's the code :
intent filters in app.json:
"scheme": "myapp",
.
.
.
"intentFilters": [
{
"action": "VIEW",
"data": [
{
"scheme": "https",
"host": "*.myapp.io",
"pathPrefix": "/"
},
{
"scheme": "https",
"host": "myapp.io",
"pathPrefix": "/"
},
{
"scheme": "myapp",
"host": "*",
"pathPrefix": "/"
}
]
}]
Config in App.tsx :
const config = {
screens: {
TabsNavigator: {
screens:{
HomeScreenNav: {
screens: {
"Home/ViewRecommendation": 'viewRecommendation/:recommendationId',
}
}
}
}
},
}
const linking = {
prefixes: [Linking.createURL('/'), 'https://app.myapp.io/'],
config
};
return (
<NavigationContainer linking={linking} ref={navigationRef}>
<RootSiblingParent>
<AppWrapper />
</RootSiblingParent>
</NavigationContainer>);
When I use myapp://viewRecommendation/recommendationId, it works but when I use https://app.myapp.io/ , it doesn't work, it just opens the brower. I'm on android and I'm using expo btw.
Does anyone have an idea what the problem is ? Thank you btw, any help would be apprechiated
I'm using Vue FullCalendar 5.3.1. I want to add event on doubleclick on empty date cell and edit event on doubleclick on event. How can I implement this? There are 2 methods by default: dateClick() and eventClick() and it's works fine for me.
My code:
<template>
<div>
<heading class="mb-6">Scheduler</heading>
<card class="custom-card">
<FullCalendar :options="calendarOptions"/>
</card>
</div>
</template>
<script>
import FullCalendar from '#fullcalendar/vue'
import dayGridPlugin from '#fullcalendar/daygrid'
import interactionPlugin from '#fullcalendar/interaction'
import resourceTimelineDay from '#fullcalendar/resource-timeline'
export default {
components: {
FullCalendar // make the <FullCalendar> tag available
},
data() {
return {
calendarOptions: {
dateClick: function(info) {
console.log(info.dateStr)
console.log(info.resource.id)
},
eventClick: function(info) {
console.log(info)
},
height: 250,
plugins: [ dayGridPlugin, interactionPlugin, resourceTimelineDay ],
headerToolbar: {
left: 'today prev,next',
center: 'title',
right: 'resourceTimelineDay,resourceTimelineWeek'
},
initialView: 'resourceTimelineDay',
aspectRatio: 1.5,
editable: true,
resourceAreaColumns: [
{
field: 'title',
headerContent: 'Worker'
}
],
resources: [
{
"id": "worker_a",
"title": "Worker A"
}, {
"id": "worker_b",
"title": "Worker B",
"eventColor": "green"
}, {
"id": "worker_c",
"title": "Worker C",
"eventColor": "orange"
}
],
events: [{
"resourceId": "worker_a",
"title": "Job 5",
"start": "2020-09-15T10:00:00+00:00",
"end": "2020-09-15T15:00:00+00:00"
}, {
"resourceId": "worker_b",
"title": "Job 2",
"start": "2020-09-15T09:00:00+00:00",
"end": "2020-09-15T14:00:00+00:00"
}, {
"resourceId": "worker_b",
"title": "Job 4",
"start": "2020-09-15T15:30:00+00:00",
"end": "2020-09-15T17:30:00+00:00"
},
]
}
}
}
}
</script>
BTW as I noticed that now all calendar settings are passed through :options = "". If you want to pass events like so <FullCalendar :events="events"/> or handle an event like <FullCalendar #dateClick="dateClick"/>, you cannot do this. Everything needs to be passed in the calendarOptions object (documentation)
The fullcalendar doesn't provide this option.
But you can attach the double click handler, when the event object is contructed by using an Event Render Hooks
In the version 5 we can use the funcion eventDidMount
data() {
return {
calendarOptions: {
...
eventDidMount: function(eventInfo){
// Not mandatory, but you can set an id to the object
eventInfo.el.id = eventInfo.event.id;
// Set the dbclick event
eventInfo.el.ondblclick = function(){
console.log(eventInfo.event)
};
}
}
}
}
Note: This works, only because this function is called only one time, in case that you are working in other version check how many times the function is called.
I am using RNPickerSelect from the library "react-native-picker-select", but cant seem to display the selection by mapping objects of an array stored in one of my state.
I tried using the same way to map objects of an array like how I would when I'm trying to display the data by wrapping in simple components but I'm getting an "Type Error. undefined is not an object (evaluating this.state.selectedItem.label".
I have declared and initialize bankResponseArray : [] within my react native class. And the API call to fetch data was successful, the state "bankResponseArray" was inserted with the data of
[
Object {
"__v": 0,
"_id": "5cb411e06f34961204003b79",
"bank_code": "RHB",
"country": "5cb04a7e23479e39e495f2b6",
"created_date": "2019-04-15T05:08:48.769Z",
"name": "RHB",
"status": true,
},
Object {
"__v": 0,
"_id": "5cb42d6635ab9132e0e0b994",
"bank_code": "Maybank",
"country": "5cb04a7e23479e39e495f2b6",
"created_date": "2019-04-15T07:06:14.701Z",
"name": "Maybank",
"status": true,
},
Object {
"__v": 0,
"_id": "5cd4e8b0c4022833942eafe0",
"bank_code": "HongLeong",
"country": "5cb04a7e23479e39e495f2b6",
"created_date": "2019-05-10T02:57:52.130Z",
"name": "HongLeong",
"status": true,
},
Object {
"__v": 0,
"_id": "5cd4ee47c4022833942eafe2",
"bank_code": "testbankcode",
"country": "5cbfc9c99b7d064464592948",
"created_date": "2019-05-10T03:21:43.534Z",
"name": "testbank",
"status": true,
},
]
And below is how my RNPickerSelect component looks like and how I'm trying to map the data from "bankResponseArray" :-
<RNPickerSelect
placeholder={{}}
useNativeAndroidPickerStyle={false}
items={this.state.bankResponseArray.map(obj =>
[{
label: obj.name,
value: obj._id,
color: "rgba(77,38,22,1)"
}]
)}
onValueChange={(value, index) => {
this.setState({
bankID: value
});
}}
onClose={() => {
// this._changeGender()
}}
style={{ ...pickerSelectStyles }}
value={this.state.businessType}
ref={el => {
this.inputRefs.picker = el;
}}
hideIcon={Platform.OS === "ios" ? false : true}
doneText={translate("common_done")}
// disabled={!canSubmit}
/>
The expected results were to have the RNPickerSelect display 4 selections which are "RHB, Maybank, HongLeong, testbank". But currently the way I'm trying to map my array to the RNPickerSelect is getting the error of "Type Error. undefined is not an object (evaluating this.state.selectedItem.label".
You should to return an object directly and not an array.
You can use like this:
items={this.state.bankResponseArray.map(obj => (
{
key: obj._id,
label: obj.name,
value: obj._id,
color: "rgba(77,38,22,1)",
}))}
I've implemented autocomplete for my address field, but the json returned from the Google Maps Places Autocomplete doesn't include the geocoded coords for the places.
There are some answers out there that don't seem to fit. For instance, this one refers to things like google.maps.places.Autocomplete(input, options); which I don't think is a thing in React Native.
Other answers appear to be based on react-native-google-places-autocomplete, but I've implemented this myself and I'd love to not do it again using that module.
Here's my method where I call the API.
async handleAddressChange() {
const url = `https://maps.googleapis.com/maps/api/place/autocomplete/json?key=${GoogleAPIKey}&input=${this.state.address}`;
try {
const result = await fetch(url);
const json = await result.json();
this.setState({ addressPredictions: json.predictions });
} catch (err) {
console.error(err);
}
}
setAddress(prediction) {
this.setState({ address: prediction.description, showPredictions: false });
}
The API response doesn't have any place or geometry property on it:
Object {
"description": "1234 Some Avenue Northeast, Washington, DC, USA",
"id": "4c79fba1b3a5ad33478b79b54896a75a4d56ca53",
"matched_substrings": Array [
Object {
"length": 4,
"offset": 0,
},
],
"place_id": "ChIJneQ1fBO5t4kRf8mTw4ieb4Q",
"reference": "ChIJneQ1fBO5t4kRf8mTw4ieb4Q",
"structured_formatting": Object {
"main_text": "1234 Some Avenue Northeast",
"main_text_matched_substrings": Array [
Object {
"length": 4,
"offset": 0,
},
],
"secondary_text": "Washington, DC, USA",
},
"terms": Array [
Object {
"offset": 0,
"value": "1600",
},
Object {
"offset": 5,
"value": "Maryland Avenue Northeast",
},
Object {
"offset": 32,
"value": "Washington",
},
Object {
"offset": 44,
"value": "DC",
},
Object {
"offset": 48,
"value": "USA",
},
],
"types": Array [
"street_address",
"geocode",
],
}
I have found a solution for the same-
Just use like this-
<GooglePlacesAutocomplete
GooglePlacesDetailsQuery={{ fields: "geometry" }}
fetchDetails={true} // you need this to fetch the details object onPress
placeholder="Search"
query={{
key: "API_KEY_GOES_HERE",
language: "en", // language of the results
}}
onPress={(data: any, details: any = null) => {
console.log("data", data);
console.log("details", details);
console.log(JSON.stringify(details?.geometry?.location));
}}
onFail={(error) => console.error(error)} />
Once you have the place id (ChIJneQ1fBO5t4kRf8mTw4ieb4Q for the example in your question), you can do a place details request.
Make sure you include the Places library in your API call: https://maps.googleapis.com/maps/api/js?libraries=places and a valid API key.
function initialize() {
var map = new google.maps.Map(document.getElementById('map-canvas'), {
center: new google.maps.LatLng(0, 0),
zoom: 15
});
var service = new google.maps.places.PlacesService(map);
service.getDetails({
placeId: 'ChIJneQ1fBO5t4kRf8mTw4ieb4Q'
}, function(place, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
// Create marker
var marker = new google.maps.Marker({
map: map,
position: place.geometry.location
});
// Center map on place location
map.setCenter(place.geometry.location);
}
});
}
initialize();
#map-canvas {
height: 160px;
}
<div id="map-canvas"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&libraries=places&callback=initialize">
</script>
I have an extremely simple React Native (Android) application as follows. Basically, I have a list of two to-do in the state, and I want to render them as consecutive <Text> elements:
export default class App extends React.Component {
constructor() {
super();
this.state = {
todos: [
{ id: 1, text: 'First todo' },
{ id: 2, text: 'Second todo' }
]
};
}
render() {
const items = this.state.todos.map((item) => {
<Text>{item.text}</Text>
});
console.log(items);
console.log(this.state.todos);
return (
<View>
<Text>Total todos: {this.state.todos.length}</Text>
{items}
</View>
);
}
}
The two console.log are fo debugging. What they output is:
Array [
undefined,
undefined,
]
Array [
Object {
"id": 1,
"text": "First todo",
},
Object {
"id": 2,
"text": "Second todo",
},
]
In other words, the {items} element is an array containing undefined (I don't know why), and consequently, when the app runs, all I see is:
Total todos: 2
Why are the two todos inside state not getting rendered?
Simply add a return statement in your map and it should do the trick ;-)
const items = this.state.todos.map((item) => {
return <Text>{item.text}</Text>
});