Update README, added example and fixed issue with plantnet
This commit is contained in:
26
README.md
26
README.md
@@ -32,6 +32,32 @@ Scientists, (app) developers, project partners, people who are lookikng for lo
|
|||||||
## Documentation
|
## Documentation
|
||||||
More in depth documentation is available on ZENODO: https://zenodo.org/record/7615472#.Y-JidC8w2gQ
|
More in depth documentation is available on ZENODO: https://zenodo.org/record/7615472#.Y-JidC8w2gQ
|
||||||
|
|
||||||
|
## Installation and setup
|
||||||
|
### Prerequisites
|
||||||
|
- Node.js
|
||||||
|
- Ionic / capactior
|
||||||
|
|
||||||
|
- Git
|
||||||
|
- Xcode (for iOS)
|
||||||
|
- Android Studio (for Android)
|
||||||
|
|
||||||
|
A Parse server (for example the one provided by Pocket Science, DIY or Back4App) is required for the app to work.
|
||||||
|
A Firebase account for login and push notifications is required.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Rename the provided .app. constant example to app.copnstnt and fill in the required fields.
|
||||||
|
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
2. Install the dependencies
|
||||||
|
npm install
|
||||||
|
3. Run the app
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Generic
|
## Generic
|
||||||
Provided functionalities in R 1.0:
|
Provided functionalities in R 1.0:
|
||||||
User login (Apple/Google/Email)
|
User login (Apple/Google/Email)
|
||||||
|
|||||||
36
src/app/app.constant.example.ts
Normal file
36
src/app/app.constant.example.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
export const ENV = {
|
||||||
|
production: false,
|
||||||
|
parseAppId: 'PARSE APP HERE',
|
||||||
|
parseServerUrl: 'PARSE URL HERE',
|
||||||
|
parseJSKey: 'parseJSKey here',
|
||||||
|
fileKey: 'parsefileKey here',
|
||||||
|
plantnetKey: 'plantnetKey here'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This file can be replaced during build by using the `fileReplacements` array.
|
||||||
|
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
|
||||||
|
// The list of file replacements can be found in `angular.json`.
|
||||||
|
|
||||||
|
export const environment = {
|
||||||
|
firebase: {
|
||||||
|
projectId: 'mobisapp-prod',
|
||||||
|
appId: 'firebase app id',
|
||||||
|
storageBucket: 'xx-prod.appspot.com',
|
||||||
|
//locationId: 'europe-west',
|
||||||
|
apiKey: 'xxx-no',
|
||||||
|
authDomain: 'xxxm',
|
||||||
|
messagingSenderId: 'xxx',
|
||||||
|
},
|
||||||
|
|
||||||
|
production: true
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For easier debugging in development mode, you can import the following file
|
||||||
|
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||||
|
*
|
||||||
|
* This import should be commented out in production mode because it will have a negative impact
|
||||||
|
* on performance if an error is thrown.
|
||||||
|
*/
|
||||||
|
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import {ENV} from '../../../app.constant';
|
import {ENV} from '../../../app.constant';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { AuthService } from '../../../services/auth.service';
|
||||||
|
import { Auth, user } from '@angular/fire/auth';
|
||||||
|
import { Storage } from '@ionic/storage-angular';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Geolocation } from '@capacitor/geolocation';
|
||||||
|
|
||||||
|
import { Http } from '@capacitor-community/http';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-classify',
|
selector: 'app-classify',
|
||||||
@@ -11,46 +19,159 @@ export class ClassifyPage implements OnInit {
|
|||||||
API_URL = 'https://my-api.plantnet.org/v2/identify/' + this.PROJECT + '?api-key=';
|
API_URL = 'https://my-api.plantnet.org/v2/identify/' + this.PROJECT + '?api-key=';
|
||||||
API_PRIVATE_KEY = ENV.plantnetKey; // secret
|
API_PRIVATE_KEY = ENV.plantnetKey; // secret
|
||||||
API_SIMSEARCH_OPTION = '&include-related-images=true'; // optional: get most similar images
|
API_SIMSEARCH_OPTION = '&include-related-images=true'; // optional: get most similar images
|
||||||
API_LANG = '&lang=fr'; // default: en
|
API_LANG = '&lang=en'; // default: en
|
||||||
|
|
||||||
IMAGE_1 = '../data/image_1.jpeg';
|
uploadSuccess = false; // Add this line
|
||||||
ORGAN_1 = 'flower';
|
image = '';
|
||||||
IMAGE_2 = '../data/image_2.jpeg';
|
user = null;
|
||||||
ORGAN_2 = 'leaf';
|
language = '';
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
altitude: number;
|
||||||
|
constructor(
|
||||||
|
|
||||||
constructor() { }
|
private storage: Storage,
|
||||||
|
private authService: AuthService,
|
||||||
|
private afAuth: Auth,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private router: Router
|
||||||
|
|
||||||
|
) { user(this.afAuth).subscribe((response) => {
|
||||||
|
//fill the user to verify if someone is logged in
|
||||||
|
this.user = response;
|
||||||
|
console.log(this.user.uid);
|
||||||
|
}); }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
// now make a POST request to the API
|
// get location
|
||||||
const xhr = new XMLHttpRequest();
|
this.getLocation();
|
||||||
xhr.open('POST', this.API_URL + this.API_PRIVATE_KEY + this.API_SIMSEARCH_OPTION + this.API_LANG, true);
|
|
||||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
||||||
xhr.onload = function () {
|
// get image from local storage
|
||||||
// do something to response
|
this.storage.get('image').then((image) => {
|
||||||
console.log(this.responseText);
|
console.log(image);
|
||||||
|
this.image = image;
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// add rose.jpg from assets directory for testing
|
||||||
|
const imageUri = 'assets/rose.jpg';
|
||||||
|
|
||||||
|
|
||||||
|
const imageType = 'image/jpeg';
|
||||||
|
const imageName = 'rose.jpg';
|
||||||
|
|
||||||
|
fetch(imageUri)
|
||||||
|
.then(response => response.blob())
|
||||||
|
.then(blob => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('images', new File([blob], imageName, { type: imageType }));
|
||||||
|
formData.append('organs', 'auto');
|
||||||
|
|
||||||
|
const url = 'https://my-api.plantnet.org/v2/identify/all';
|
||||||
|
const headers = {
|
||||||
|
'accept': 'application/json',
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
};
|
||||||
|
const params = {
|
||||||
|
'include-related-images': 'false',
|
||||||
|
'no-reject': 'false',
|
||||||
|
'lang': 'en',
|
||||||
|
'api-key': '2b10bmIKkNNcBL6D4jwq3il4rO'
|
||||||
|
};
|
||||||
|
|
||||||
|
Http.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: url,
|
||||||
|
headers: headers,
|
||||||
|
params: params,
|
||||||
|
data: formData
|
||||||
|
}).then(response => {
|
||||||
|
console.log(response.data);
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
xhr.send(JSON.stringify({
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: this.IMAGE_1,
|
|
||||||
organ: this.ORGAN_1
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
savetoParse() {
|
||||||
|
|
||||||
|
const plantnet_data_store = Parse.Object.extend('plantnet_data');
|
||||||
|
|
||||||
|
// create new instance of parse class
|
||||||
|
|
||||||
|
const plantnet_data = new plantnet_data_store();
|
||||||
|
|
||||||
|
// set value for parse clas
|
||||||
|
|
||||||
|
const file = new Parse.File('plantnet_image.jpg', { base64: this.image });
|
||||||
|
file.save().then(
|
||||||
|
(file) => {
|
||||||
|
console.log(file);
|
||||||
},
|
},
|
||||||
{
|
(error) => {
|
||||||
url: this.IMAGE_2,
|
console.log(error);
|
||||||
organ: this.ORGAN_2
|
|
||||||
|
|
||||||
}
|
}
|
||||||
]
|
);
|
||||||
}));
|
// add location and clasiification
|
||||||
|
|
||||||
|
plantnet_data.set('user_uid', this.user.uid);
|
||||||
|
plantnet_data.set('name', this.user.displayName);
|
||||||
|
plantnet_data.set('email', this.user.email);
|
||||||
|
plantnet_data.set('image', file);
|
||||||
|
plantnet_data.set('latitude', this.latitude);
|
||||||
|
plantnet_data.set('longitude', this.longitude);
|
||||||
|
plantnet_data.set('altitude', this.altitude);
|
||||||
|
|
||||||
|
|
||||||
|
plantnet_data.save().then(
|
||||||
|
(result: any) => {
|
||||||
|
console.log(result);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLocation() {
|
||||||
|
const position = await Geolocation.getCurrentPosition({
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
});
|
||||||
|
this.latitude = position.coords.latitude;
|
||||||
|
console.log(position.coords.latitude);
|
||||||
|
this.longitude = position.coords.longitude;
|
||||||
|
this.altitude = position.coords.altitude;
|
||||||
|
return position.coords;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Take a picture
|
|||||||
|
|
||||||
<ion-content [fullscreen]="false">
|
<ion-content [fullscreen]="false">
|
||||||
<ion-card>
|
<ion-card>
|
||||||
Welcome to the Plantnet API
|
<ion-card-header>Welcome to the Plantnet API</ion-card-header>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
||||||
<div *ngIf="image">
|
<div *ngIf="image">
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import * as Parse from 'parse';
|
|
||||||
import { ENV } from '../../app.constant';
|
|
||||||
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
|
import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
|
||||||
import { ModalController } from '@ionic/angular';
|
import { ModalController } from '@ionic/angular';
|
||||||
import { PreviewPage } from './preview/preview.page';
|
import { PreviewPage } from './preview/preview.page';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { AuthService } from '../../services/auth.service';
|
|
||||||
import { Auth, user } from '@angular/fire/auth';
|
|
||||||
import { Storage } from '@ionic/storage-angular';
|
import { Storage } from '@ionic/storage-angular';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Geolocation } from '@capacitor/geolocation';
|
|
||||||
|
|
||||||
Parse.initialize(ENV.parseAppId, ENV.parseJSKey);
|
|
||||||
(Parse as any).serverURL = ENV.parseServerUrl; // use your server url
|
|
||||||
|
|
||||||
let uploadSuccess = false;
|
let uploadSuccess = false;
|
||||||
|
|
||||||
@@ -33,37 +28,23 @@ export class PlantnetPage implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private modal: ModalController,
|
private modal: ModalController,
|
||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private authService: AuthService,
|
|
||||||
private afAuth: Auth,
|
|
||||||
private storage: Storage,
|
private storage: Storage,
|
||||||
private router: Router
|
private router: Router
|
||||||
) {
|
) {
|
||||||
user(this.afAuth).subscribe((response) => {
|
|
||||||
//fill the user to verify if someone is logged in
|
|
||||||
this.user = response;
|
|
||||||
console.log(this.user.uid);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
this.getLocation();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLocation() {
|
|
||||||
const position = await Geolocation.getCurrentPosition({
|
|
||||||
enableHighAccuracy: true,
|
|
||||||
});
|
|
||||||
this.latitude = position.coords.latitude;
|
|
||||||
console.log(position.coords.latitude);
|
|
||||||
this.longitude = position.coords.longitude;
|
|
||||||
this.altitude = position.coords.altitude;
|
|
||||||
return position.coords;
|
|
||||||
}
|
|
||||||
async openCamera() {
|
async openCamera() {
|
||||||
const modal = await this.modal.create({
|
const modal = await this.modal.create({
|
||||||
component: PreviewPage,
|
component: PreviewPage,
|
||||||
@@ -71,49 +52,17 @@ export class PlantnetPage implements OnInit {
|
|||||||
animated: true,
|
animated: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
modal.onDidDismiss().then((data) => {
|
modal.onDidDismiss().then(async (data) => {
|
||||||
if (data !== null) {
|
if (data !== null) {
|
||||||
this.image = data.data;
|
this.image = data.data;
|
||||||
|
|
||||||
this.uploadSuccess = true;
|
this.uploadSuccess = true;
|
||||||
// create parse class
|
// create parse class
|
||||||
|
|
||||||
const plantnet_data_store = Parse.Object.extend('plantnet_data');
|
|
||||||
|
|
||||||
// create new instance of parse class
|
// store image in local storage
|
||||||
|
await this.storage.set('plantnet_image', this.image);
|
||||||
const plantnet_data = new plantnet_data_store();
|
} else {
|
||||||
|
|
||||||
// set value for parse clas
|
|
||||||
|
|
||||||
const file = new Parse.File('image.jpg', { base64: this.image });
|
|
||||||
file.save().then(
|
|
||||||
(file) => {
|
|
||||||
console.log(file);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// add location and clasiification
|
|
||||||
|
|
||||||
plantnet_data.set('user_uid', this.user.uid);
|
|
||||||
plantnet_data.set('name', this.user.displayName);
|
|
||||||
plantnet_data.set('email', this.user.email);
|
|
||||||
plantnet_data.set('image', file);
|
|
||||||
plantnet_data.set('latitude', this.latitude);
|
|
||||||
plantnet_data.set('longitude', this.longitude);
|
|
||||||
plantnet_data.set('altitude', this.altitude);
|
|
||||||
|
|
||||||
plantnet_data.save().then(
|
|
||||||
(result: any) => {
|
|
||||||
console.log(result);
|
|
||||||
},
|
|
||||||
(error: any) => {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log('no data');
|
console.log('no data');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export class PreviewPage implements OnInit {
|
|||||||
|
|
||||||
launchCamera() {
|
launchCamera() {
|
||||||
const cameraPreviewOptions: CameraPreviewOptions = {
|
const cameraPreviewOptions: CameraPreviewOptions = {
|
||||||
position: 'front', // front or rear
|
position: 'rear', // front or rear
|
||||||
parent: 'content', // the id on the ion-content
|
parent: 'content', // the id on the ion-content
|
||||||
className: '',
|
className: '',
|
||||||
width: window.screen.width, //width of the camera display
|
width: window.screen.width, //width of the camera display
|
||||||
|
|||||||
Reference in New Issue
Block a user