Commit 807c52bc authored by Bastien Zigmann's avatar Bastien Zigmann Committed by Vincent Dubois

Ajout de la doc en markdown et push de mes dernières modifications non...

Ajout de la doc en markdown et push de mes dernières modifications non terminées (expliquées dans la doc)
parent 71dae0bc
......@@ -52,6 +52,8 @@ class Geocropping {
* @memberof Geocropping
*/
_geoJSONLayers: L.GeoJSON;
// ENCOURS
_geoJSONLayersFar: L.GeoJSON;
/**
* Dernière position de l'utilisateur enregistré
*
......@@ -133,6 +135,10 @@ class Geocropping {
*/
_app_distance_hide: number = 55;
// ENCOURS
_app_distance_search_far: number = 1000;
_app_distance_hide_far: number = 1000;
/**
* Distance need to search feature between the actual user location and the latest search location (meter)
*
......@@ -178,6 +184,46 @@ class Geocropping {
* STYLE SETTINGS
*/
// ENCOURS
_style_alert_far: L.PathOptions = {
fillColor: "#ee0000",
color:"#ff0000",
weight: 1,
opacity: 1,
fillOpacity: 1.0,
fill: true
};
// ENCOURS
_style_warning_far: L.PathOptions = {
fillColor: "#ee6700",
color:"#ff7800",
weight: 1,
opacity: 1,
fillOpacity: 1.0,
fill: true
};
// ENCOURS
_style_ok_far: L.PathOptions = {
fillColor: "#00ee00",
color:"#00ff00",
weight: 1,
opacity: 1,
fillOpacity: 1.0,
fill: true
};
// ENCOURS
_style_invalid_far: L.PathOptions = {
fillColor: "#000000",
color: "#000000",
weight: 1,
opacity: 1,
fillOpacity: 1.0,
fill: true
};
/**
* Style rouge pour un element sur la carte
*
......@@ -311,6 +357,8 @@ class Geocropping {
title: 'User'
});
this._geoJSONLayers = L.geoJSON();
// ENCOURS
this._geoJSONLayersFar = L.geoJSON();
// vérifier si l'utilisateur est connecté et récuppérer ses informations
this.updateUserDetails();
......@@ -1000,7 +1048,7 @@ class Geocropping {
// appel la fonction style dans geojson
this._geoJSONLayers.resetStyle((popupEvent.popup as any)._source);
//TODO: arrêter seulement sur l'évenement updatePopup et pas sur tout l'évent
// TODO: arrêter seulement sur l'évenement updatePopup et pas sur tout l'évent
$(window).off('resize orientationchange');
})
......@@ -1072,7 +1120,7 @@ class Geocropping {
let year = dateOld.getUTCFullYear();
dateOld.setUTCFullYear(year - 2);
// A été invalidé (note ouverte)
// A été invalidé
if (this._featureDisplayed[feature.id] && tags.fixme) {
// noir
style = $.extend({}, this._style_invalid); // $.extend pour ne pas modifier les valeur de base (ne pas faire une référence)
......@@ -1122,7 +1170,124 @@ class Geocropping {
}
}
}).addTo(this._map);
});
this._geoJSONLayers.addTo(this._map);
// ENCOURS
this._geoJSONLayersFar = L.geoJSON(null, {
// Mettre un cercle pour les point a la place des marker
pointToLayer: (feature, latlng) => {
//console.log('pointToLayer', feature, latlng);
return new L.CircleMarker(latlng);
},
filter: (featureData: any) => {
// Pour chaque element dire si il doit être affiché
let display = true;
let hasATagTranslatable = (tags) => {
let tagTranslated = false;
$.each(tags, (index, value) => {
if (tagTranslated) {
return;
}
if (presets) {
if (presets[`${index}/${value}`] || presets[index]) {
tagTranslated = true;
}
}
});
return tagTranslated;
}
// Si il existe déjà, ne pas l'afficher une deuxième fois
if (this._featureDisplayed[featureData.id] !== undefined ||
featureData.properties.type !== 'node' ||
!hasATagTranslatable(featureData.properties.tags)) {
display = false;
}
/*for (let tag in featureData.properties.tags) {
if (presets[`${tag}/${featureData.properties.tags[tag]}`] || presets[tag]) {
console.log(`${tag}/${featureData.properties.tags[tag]}`, presets[`${tag}/${featureData.properties.tags[tag]}`], tag, presets[tag]);
display = true;
break;
} else {
console.info(`${tag}/${featureData.properties.tags[tag]}`, presets[`${tag}/${featureData.properties.tags[tag]}`], tag, presets[tag]);
}
}*/
return display;
},
style: (feature) => {
// Style des élément qui seront affiché
//console.log(feature);
//console.log(feature.properties);
let style: L.PathOptions = {};
if ((feature.properties as any).tags) {
let tags = (feature.properties as any).tags;
let dateVerified: Date;
let dateChange = new Date((feature.properties as any).meta.timestamp);
if (tags["survey:date"]) {
dateVerified = new Date(tags["survey:date"]);
} else if (tags["source"] === "survey" && tags["source:date"]) {
dateVerified = new Date(tags["source:date"]);
}
// Date déterminant un élément comme ancien
let dateOld = new Date();
let year = dateOld.getUTCFullYear();
dateOld.setUTCFullYear(year - 2);
// A été invalidé
if (this._featureDisplayed[feature.id] && tags.fixme) {
// noir
style = $.extend({}, this._style_invalid_far); // $.extend pour ne pas modifier les valeur de base (ne pas faire une référence)
}
// Jamais validé et ancien
else if (!dateVerified && dateChange < dateOld) {
//red
style = $.extend({}, this._style_alert_far); // $.extend pour ne pas modifier les valeur de base (ne pas faire une référence)
}
// Jamais validé et récent
else if (!dateVerified && dateChange > dateOld) {
//orange
style = $.extend({}, this._style_warning_far); // $.extend pour ne pas modifier les valeur de base (ne pas faire une référence)
}
// Validé et ancien
else if (dateVerified && dateChange < dateOld) {
//orange
style = $.extend({}, this._style_warning_far); // $.extend pour ne pas modifier les valeur de base (ne pas faire une référence)
}
// Validé et récent
else {
//gree
style = $.extend({}, this._style_ok_far); // $.extend pour ne pas modifier les valeur de base (ne pas faire une référence)
}
}
/*if (feature.geometry.type === "Point") {
style.fill = true;
} else {
style.weight = 3;
}*/
return style;
},
});
// ENCOURS
this._map.on('zoomend', (e) => {
if(this._map.getZoom() <=16 ){
this._map.removeLayer(this._geoJSONLayers);
this._geoJSONLayersFar.addTo(this._map);
}else{
this._map.removeLayer(this._geoJSONLayersFar);
this._geoJSONLayers.addTo(this._map);
}
});
this._checkChanges();
}
......@@ -1216,7 +1381,7 @@ class Geocropping {
if (this._lastUserLocation === undefined || this._lastUserLocation === null || this._lastUserLocation.distanceTo(data.latlng) > this._app_distance_travel_to_search) {
this._lastUserLocation = data.latlng;
this._userMarker.setLatLng(data.latlng).addTo(this._map);
this._searchDataAround(data.latlng);
this._searchData(data.latlng);
}
if (options && options.forceWatch === true) { // relancer la géolocalisation
setTimeout(() => {
......@@ -1258,11 +1423,13 @@ class Geocropping {
return;
let lat = pos.lat, lon = pos.lng;
// Vérifier si la distance pour cacher les élément est plus petite que celle pour les afficher
let distance = this._app_distance_hide < this._app_distance_search ? this._app_distance_hide : this._app_distance_search;
// Requete overpass (actuellement récuépérer les node ayant au moins un tag)
let query = `[out:json];node[~"."~"."](around:${distance},${lat},${lon})->.nodes;/*way(around:${distance},${lat},${lon})->.ways;*/.nodes out meta;/*.ways out geom meta;*/`;
$.ajax({
context: this,
url: this._app_server_interpreter_list[this._app_server_interpreter_index],
......@@ -1274,10 +1441,10 @@ class Geocropping {
// Traiter les éléments si ils existent
if (data.elements.length > 0) {
let geojson = osmtogeojson(data);
//Ajouter les donners sur la carte
//Ajouter les données sur la carte
this._geoJSONLayers.addData(geojson);
// Reset la list des données affichés
// Reset la liste des données affichées
this._featureDisplayed = {};
this._geoJSONLayers.getLayers().forEach((layer: L.Layer, index, array) => {
......@@ -1352,7 +1519,7 @@ class Geocropping {
/**
* Récupérer les données des éléments affichés directement sur OSM
*
* @returns {JQueryPromise<{}>}
* @returns {JQueryPromise<{}>} pid
* @memberof Geocropping
*/
_searchInfoElementsOSM(): JQueryPromise<{}> {
......@@ -1398,7 +1565,7 @@ class Geocropping {
// Récupérer l'élément afficher
let feature = this._featureDisplayed[`${type}/${element["@attributes"].id}`]
if (feature && element["@attributes"]) {
// Faire les modification necessaire
if (element["@attributes"].visible) {
......@@ -1573,7 +1740,160 @@ class Geocropping {
}
isFixmeFromUs(contenuDuFixme: string){
// à défault de mieux
return contenuDuFixme === 'location' || contenuDuFixme === 'absent' || contenuDuFixme === 'tags' || contenuDuFixme === 'transient' || contenuDuFixme === 'resurvey';
}
// ENCOURS
_searchDataFar(pos: L.LatLng) {
let lat, lon;
lat = this._lastUserLocation.lat;
lon = this._lastUserLocation.lng;
let distance = this._app_distance_hide_far < this._app_distance_search_far ? this._app_distance_hide_far : this._app_distance_search_far;
// Requete overpass (actuellement récuépérer les node ayant au moins un tag)
let query = `[out:json];node[~"."~"."](around:${distance},${lat},${lon});out meta;`;
$.ajax({
context: this,
url: this._app_server_interpreter_list[this._app_server_interpreter_index],
data: {
data: query
},
success: (data, textStatus, jqXHR) => {
// console.log(data);
this._updateElementsDisplayedFar(data);
console.log(this._featureDisplayed);
// let ids = [];
// for (var index = 0; index < data.elements.length; index++){
// if(index < 10)
// ids.push(data.elements[index].id);
// }
// var req = OauthOsm.apiRequest.GetElementsId; // 200 max
// req.params.type = "node";
// req.params.ids = ids;
// this._osm._sendRequest(req).then((data2) => { console.log(data2)});
// Traiter les éléments si ils existent
if (data.elements.length > 0) {
let geojson = osmtogeojson(data);
//Ajouter les données sur la carte
this._geoJSONLayers.addData(geojson);
// Reset la liste des données affichées
this._featureDisplayed = {};
this._geoJSONLayers.getLayers().forEach((layer: L.Layer, index, array) => {
/**
* Supprimer les éléments distant
*/
let remove = false;
if (layer instanceof L.CircleMarker) {
let dist = layer.getLatLng().distanceTo(this._lastUserLocation);
if (dist > this._app_distance_hide) {
remove = true;
}
} else if (layer instanceof L.Polyline) {
let nodes: any = layer.getLatLngs();
if (nodes[0] instanceof Array) {
nodes = nodes[0];
}
remove = true;
// Ne pas supprimer si un des points est a portée
for (let i = 0; i < nodes.length; i++) {
let dist = L.latLng(nodes[i].lat, nodes[i].lng).distanceTo(pos);
if (dist < this._app_distance_hide) {
remove = false;
break;
}
}
}
if (remove) {
this._geoJSONLayers.removeLayer(layer);
} else {
this._featureDisplayed[((layer as any).feature as any).id] = { layer: layer };
}
});
// récupérer les information des Point affiché directement sur osm (ne pas avoir le problème de cache des serveur Overpass)
this._searchInfoElementsOSM().then(data => {
this._updateElementsDisplayed(data);
});
}
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(errorThrown);
}
});
}
// ENCOURS
_updateElementsDisplayedFar(data: { any?}){
let type;
let obj = [];
//// Transformer les données en données Overpass pour les changer en geoJSON
for (var key in data) {
if (data.hasOwnProperty(key) && key === "elements") {
type = key;
obj = data[type];
}
}
if (!(obj instanceof Array) && obj instanceof Object) {
obj = [obj];
}
for (let index = 0; index < obj.length; index++) {
let element = obj[index];
// Récupérer l'élément afficher
let feature = this._featureDisplayed[`${type}/${element.id}`]
if (feature && element) {
// Faire les modification necessaire
if (element.visible) {
delete element.visible;
}
let osm = {
type: type
};
for (let key in element) {
if (element.hasOwnProperty(key)) {
let attribute = element.key;
if ((Number as any).isInteger(attribute)) {
attribute = Number(attribute);
}
osm[key] = attribute;
}
}
let tags = element['tag'];
if (tags) {
osm['tags'] = {};
if (!(tags instanceof Array) && tags instanceof Object) {
tags = [tags];
}
for (let i = 0; i < tags.length; i++) {
let tag = tags[i];
osm['tags'][tag["@attributes"].k] = tag["@attributes"].v;
}
}
// Affecter les nouvelles données
(feature.layer as any).feature = osmtogeojson({ elements: [osm] }).features[0];
this._geoJSONLayers.resetStyle(feature.layer);
}
}
}
_searchData(pos: L.LatLng){
if(this._map.getZoom() < 17){
this._searchDataFar(pos);
}else{
this._searchDataAround(pos);
}
}
}
......@@ -122,7 +122,7 @@ class OauthOsm {
},
// online=true
UpdateSingleUserPreference: {
method: 'POST',
method: 'PUT',
url: '/',
request: 'UpdateSingleUserPreference',
params: {
......
......@@ -129,10 +129,7 @@ $(document).ready(() => {
$('.map-menu-item:has(#userImage)').show();
$('#userInfo').show();
}
/**
* Met à jour l'affichage des statistiques
*
*/
let updateStats = () => {
$('#userNbValid').text(`Validations : ${geocropping._userNbValidated}`);
$('#userNbInvalid').text(`Signalements : ${geocropping._userNbInvalidated}`);
......
......@@ -12,7 +12,7 @@
"declaration": true,
"declarationDir": "./typings",
"sourceMap": true,
"inlineSources": false,
"inlineSources": true,
"listFiles": true
},
"include": [
......
......@@ -45,6 +45,7 @@ declare class Geocropping {
* @memberof Geocropping
*/
_geoJSONLayers: L.GeoJSON;
_geoJSONLayersFar: L.GeoJSON;
/**
* Dernière position de l'utilisateur enregistré
*
......@@ -110,6 +111,8 @@ declare class Geocropping {
* @memberof Geocropping
*/
_app_distance_hide: number;
_app_distance_search_far: number;
_app_distance_hide_far: number;
/**
* Distance need to search feature between the actual user location and the latest search location (meter)
*
......@@ -145,6 +148,10 @@ declare class Geocropping {
* @memberof Geocropping
*/
notify: MyNotification;
_style_alert_far: L.PathOptions;
_style_warning_far: L.PathOptions;
_style_ok_far: L.PathOptions;
_style_invalid_far: L.PathOptions;
/**
* Style rouge pour un element sur la carte
*
......@@ -298,7 +305,7 @@ declare class Geocropping {
/**
* Récupérer les données des éléments affichés directement sur OSM
*
* @returns {JQueryPromise<{}>}
* @returns {JQueryPromise<{}>} pid
* @memberof Geocropping
*/
_searchInfoElementsOSM(): JQueryPromise<{}>;
......@@ -316,4 +323,9 @@ declare class Geocropping {
_ajoutPreferenceUnique(id: any, version: any): void;
_getStats(): void;
isFixmeFromUs(contenuDuFixme: string): boolean;
_searchDataFar(pos: L.LatLng): void;
_updateElementsDisplayedFar(data: {
any?;
}): void;
_searchData(pos: L.LatLng): void;
}
Documentation Geocropping
[Sources](https://framagit.org/renaudzigmann/Geocropping)
[Site](https://geocropping.xsalto.com/)
[Site dev](https://geocropping-dev.xsalto.com/)
1\. Présentation du projet
Geocropping est une site web permettant la validation de données
[OpenStreetMap](https://www.openstreetmap.org/) autour de l’utilisateur,
permettant ainsi une maintenance des bases de données facilement. Il
utilise la library [Leaflet](http://leafletjs.com/) ainsi que des
[plugins](http://leafletjs.com/plugins.html) proposés par cette library.
Le fonctionnement est le suivant : lors de la géolocalisation de
l’utilisateur, on scanne les alentours pour afficher les objets s’y
trouvant, on efface ensuite les objets lorsque l’utilisateur s’en
éloigne trop (noter que la distance d’affichage est plus petite que la
distance de suppression).
Pour l’affichage et les requêtes, voici le fonctionnement : On interroge
[Overpass API](http://wiki.openstreetmap.org/wiki/Overpass_API) sur les
positions des nodes proches, si il en existe, on interroge ensuite
directement la base de donnée d’OSM (OpenStreetMap) pour savoir la
couleur de l’élément à afficher.
Le code couleur est le suivant :
La validation se fait en ajoutant une tag survey:date (ou source =survey
& source:date=…) à l’élément sur OSM ou en le mettant à jour.
L’invalidation quant à elle se fait en ajoutant un tag
[fixme](http://wiki.openstreetmap.org/wiki/Key:fixme) à l’objet
La connexion à la base de donnée d’OSM se fait par le protocole
[Oauth](https://fr.wikipedia.org/wiki/OAuth). Celui-ci se fait en trois
étapes :
1. Demande d’identifiants de connexion au près du server à l’aide des
identifiants d’application.
2. Demande de connexion de l’utilisateur sur OSM. On envoi
l’utilisateur sur le site pour qu’il s’y connecte, puis il est
redirigé sur Geocropping (le callback).
3. Demande de l’access token à OSM pour récupérer la session
de l’utilisateur.
Une fois ces étapes accomplie, l’utilisateur peu enfin executer des
requêtes sur le server OSM.
Lors d’une invalidation, l’utilisateur a la possibilité de cocher une
checkbox pour « suivre » le point, lorsque cela est fait, on ajout une
nouvelle préférence OSM à l’utilisateur. Les préférences sont de la
forme clé,valeur avec la clé qui est l’identifiant du node à suivre et
la valeur la version du node au moment de son ajout.
En cours : Lorsque l’on dézoom suffisament, la carte devrait afficher
les nodes avec juste un plus petit point de couleur non clickable au
lieu des cercles habituels. Et ce dans un cercle bien plus large que
lorsque l’on parcours tout simplement les rues. Cela a pour but de
pouvoir suivre l’avancement de la zone et d’avoir une vue d’ensemble.
2\. IHM
L’IHM est volontairement simplifié, nous avons donc une carte avec des
boutons de zoom/dézoom en haut à gauche, l’utilisateur localisé au
centre de l’écran. Le déplacement sur la carte est bloqué, il se
recentre automatiquement sur l’utilisateur.
Lors de ses déplacements, l’utilisateur va voir apparaître des cercles
de couleurs sur la carte. Ces cercles représentent les objets (nodes)
déclaré autour de lui et, selon le code couleur, à valider ou non.
Le menu de droite est ouvrable en clickant sur le bouton en haut à
droite. Lorsque celui-ci s’ouvre, on y découvre :
- un bouton de connexion/déconnexion
- des statistiques sur l’utilisateur : le nombre de
validations/signalements qu’il a fait
- le choix du server (Mondial ou Français actuellement)
- une option « Position au clic » permettant de désactiver la
géolocalisation et le blocage de la carte sur l’utilisateur. Une fois
cette option cochée, l’utilisateur peut alors changer sa position sur la
carte par un simple clic à l’endroit où il souhaite être localisé.
- une option « Dev API », disponible uniquement sur la version dev du
site et permettant de se connecter au server dev de OSM pour pouvoir
faire des tests (désormais cachée car obsolète mais peut être
dé-commentée et mise à jour).
- un lien « Mode d’emploi » permettant d’accéder à une explication sur
le fonctionnement du site, le code couleur...etc…
- un lien « Framagit » redirigeant vers le dépôt en ligne avec les
sources du site.
- un lien vers la liste des commits
Si l’utilisateur est connecté à son compte OpenStreetMap, il verra son
avatar apparaître sous l’icône du menu.
Des mini fenêtre de notification apparaissent dans le coin inférieur
droit de l’écran pour confirmer ou non si les actions que l’utilisateur
fait s’effectuent bien.
3\. Code et overpass API
Documentation générée par typedoc disponible
[ici](https://geocropping-dev.xsalto.com/doc/)
.ts : sources en typescript ou juste en javascript.
- Geocropping.ts :
Fichier principale du projet, il contient la class Geocropping ainsi que
des fonctions concernant : l’initialisation de la carte, la connexion et
le renseignement sur l’utilisateur, la localisation, la recherche et
l’affichage des données ainsi que quelques autre fonctions utiles.
IsMapCreated() : qui vérifie si la map est instanciée
createMap(Map) : crée la map et l’affiche
connect() : Connecte l’utilisateur à OSM
isConnect() : Vérifie si l’utilisateur est connecté
userDisconnect() : Déconnecte l’utilisateur d’OSM
setClickPosition(active:boolean) : activer/désactiver la position au
click