Dans cet article, je souhaite documenter comment écrire des requêtes géospatiales sur la base de données autonome Oracle. Cet exemple utilisera le module complémentaire node-oracledb pour Node.js et s’exécutera sur une base de données Oracle JSON. Je ne parlerai pas de l’approvisionnement d’une base de données JSON sur Oracle Cloud – cela sera laissé comme exercice à l’utilisateur, s’il le souhaite. Ce premier élément concerne la configuration de l’environnement de développement, la création du projet Node.JS et l’installation de la configuration et des logiciels de prise en charge requis pour se connecter à la base de données distante.
J’étudiais cette fonctionnalité lors de la création d’une application téléphonique simple qui afficherait les lieux d’intérêt en fonction des coordonnées actuelles de l’utilisateur. J’ai créé une petite application Node.js côté serveur qui s’exécutait dans un cluster Kubernetes frappant une base de données Oracle JSON. Le code présenté ici est une simplification qui illustre simplement l’utilisation de l’API. Dans l’application, l’utilisateur sélectionnerait la distance par rapport à sa position actuelle pour voir tous les lieux d’intérêt à proximité. Ce qui est bien dans cet exemple, c’est que le code est entièrement du côté du développeur (Visual Studio sur Mac) accédant à la base de données distante. Cela facilite le débogage et l’itération.
L’environnement de développement
Tout d’abord, provisionnez une base de données JSON sur Oracle Cloud. Essayez-le sur Oracle Free Tier :
Ensuite, créez un nouveau projet Node.js.
Installez les bibliothèques de base de données Oracle du nœud.
Je l’exécute à l’aide de Visual Code sur macOS et je me connecte à ma base de données JSON distante, il y a donc 2 éléments supplémentaires nécessaires pour se connecter : un portefeuille Oracle pour des connexions sécurisées et un client instantané Oracle pour macOS.
Téléchargez le portefeuille pour votre base de données Oracle JSON. Notez le nom TNS qui sera également nécessaire pour construire le client Oracle.
IMPORTANT: Vous devrez mettre à jour sqlnet.ora pour pointer vers l’emplacement du portefeuille. Modifiez sqlnet.ora dans le dossier du portefeuille et mettez à jour :
WALLET_LOCATION = (SOURCE = (METHOD = file) (METHOD_DATA = (DIRECTORY="/Users/myuser/myProject/wallet")))
SSL_SERVER_DN_MATCH=yes
Téléchargez le client instantané pour votre plateforme de développement ; dans ce cas, macOS.
La structure de répertoires de votre projet devrait ressembler à ceci :
user@user-mac myProject % ls
TestIt.js
instantclient_19_8
node_modules
package-lock.json
package.json
wallet
Le code
Que sont les requêtes GeoSpatial ? Ce sont des requêtes sur un ensemble de types de données GeoJSON (Points
, Polygons
etc.).
Oracle fournit un ensemble d’opérateurs QBE ($near
, $within
et $intersects
) qui peut trouver un ensemble d’emplacements en fonction des paramètres de requête. La spécification GeoJSON spécifie un point comme [longitude, latitude]. Peut-être que quelqu’un peut m’expliquer cela, car j’ai toujours pensé que les emplacements étaient donnés en « latitude, longitude », alors gardez cela à l’esprit lors du stockage des points GeoJSON.
Pourquoi [longitude, latitude]? Il correspond à un système de coordonnées cartésien, [x,y]. C’est la théorie. Alors, où est passé [latitude, longitude] origine ? J’ai demandé sur internet et j’ai trouvé ceci :
Je ne suis pas un expert dans ce domaine, mais j’ai fait quelques lectures sur le sujet, notamment sur son histoire. Je pense que la raison en est que la mesure précise de la latitude est venue en premier car elle était basée sur des mesures astronomiques. La longitude n’était pas mesurable avec précision jusqu’à ce qu’un appareil de mesure du temps très précis ait été développé.
Regardons le code. Tout d’abord, importez le oracledb
forfait. Cet exemple définit autoCommit
à true
.
const oracledb = require('oracledb');
oracledb.autoCommit = true;
Initialiser le OracleClient
en passant dans l’emplacement du wallet et de l’instant client :
oracledb.initOracleClient({
libDir: "/Users/myuser/myProject/instantclient_19_8",
configDir: "/Users/myuser/myProject/wallet"
});
Connectez-vous maintenant à la base de données. Le TNSNAME est extrait de la même page où le portefeuille a été téléchargé.
async function connect() {
await oracledb.createPool({
user: 'admin',
password: 'password',
connectString: 'tnsName',
})
.then(pool => this._connectionPool = pool)
return this._connectionPool
}
Une fois connecté, créez une nouvelle Collection.
const myConnectionPool = await connect();
const myConnection = await myConnectionPool.getConnection();
const mySodaDB = myConnection.getSodaDatabase();
const myCollection = await mySodaDB.createCollection('GeoJsonExample');
Chargez des points GeoJSON avec une longitude décroissante et une latitude croissante :
// Load up some locations
for (let i = 0; i < 10; i++) {
latitude = 30 + (i*.1);
longitude = 30 - (i*.1);
let myContent = {
"location": {
"type": "Point",
"coordinates": [
longitude,
latitude
]
}
};
await myCollection.insertOne(myContent);
}
Les points de la collection sont les suivants :
Inital Load of GeoJson Points
{"location":{"type":"Point","coordinates":[29.8,30.2]}}
{"location":{"type":"Point","coordinates":[29.7,30.3]}}
{"location":{"type":"Point","coordinates":[29.6,30.4]}}
{"location":{"type":"Point","coordinates":[29.5,30.5]}}
{"location":{"type":"Point","coordinates":[29.4,30.6]}}
{"location":{"type":"Point","coordinates":[29.3,30.7]}}
{"location":{"type":"Point","coordinates":[29.2,30.8]}}
{"location":{"type":"Point","coordinates":[29.1,30.9]}}
{"location":{"type":"Point","coordinates":[30,30]}}
{"location":{"type":"Point","coordinates":[29.9,30.1]}}
Maintenant que nous avons des points, trouvez des points proches d’un point spécifique (dans ce cas, 60 miles) et enregistrez-les. Chacun des opérateurs spatiaux QBE est suivi d’un objet JSON dont les champs doivent inclure $geometry
. Opérateur $near
doit également inclure le champ $distance
et il peut inclure $unit
. Une erreur de compilation est déclenchée si $geometry
est manquant ou si $distance
ou $unit
est présent avec l’opérateur $intersects
ou $within
(source : documentation Oracle).
// Find all within a mile
documents = await myCollection.find().filter({"location":{"$near":{"$geometry":{"type":"Point","coordinates":[30,30]},"$distance":60,"$unit":"mile"}}}).getDocuments();
contentOfDocs = documents.map(i => i.getContent());
console.log("Locations within a mile of corrdinate [30,30]")
contentOfDocs.forEach(logIt);
console.log();
Les points renvoyés par cette requête sont les suivants :
Locations within 60 miles of corrdinate [30,30]
{"location":{"type":"Point","coordinates":[29.9,30.1]}}
{"location":{"type":"Point","coordinates":[29.8,30.2]}}
{"location":{"type":"Point","coordinates":[29.7,30.3]}}
{"location":{"type":"Point","coordinates":[29.6,30.4]}}
{"location":{"type":"Point","coordinates":[29.5,30.5]}}
{"location":{"type":"Point","coordinates":[29.4,30.6]}}
{"location":{"type":"Point","coordinates":[30,30]}}
Indiquer [29.3,30.7] est à plus de 60 milles. Pour vérifier, j’utilise le calculateur de distance de latitude/longitude de la NOAA, qui montre que ce point est à 55 milles marins, soit 55 * 1,1508 = 63,3 milles terrestres.
Pour voir toutes les unités de mesure disponibles, exécutez :
select * from SDO_UNITS_OF_MEASURE;
Il existe actuellement plus de 140 unités de mesure distinctes.
Cette requête recherchera tous les points contenus dans le polygone spécifié à l’aide de la $within
Opérateur QBE. Notez que les coordonnées sont spécifiées comme si vous dessiniez le polygone. C’est un carré spécifié par 5 points.
// Find all within a polygon,in this case a box
documents = await myCollection.find().filter({"location":{"$within":{"$geometry":{"type":"Polygon","coordinates":[[[29,30],[30,30],[30,31],[29,31],[29,30]]]}}}}).getDocuments();
contentOfDocs = documents.map(i => i.getContent());
console.log("Locations within specified polygon")
contentOfDocs.forEach(logIt);
console.log();
Cette requête renvoie 9 points :
Locations within specified polygon
{"location":{"type":"Point","coordinates":[29.8,30.2]}}
{"location":{"type":"Point","coordinates":[29.7,30.3]}}
{"location":{"type":"Point","coordinates":[29.6,30.4]}}
{"location":{"type":"Point","coordinates":[29.5,30.5]}}
{"location":{"type":"Point","coordinates":[29.4,30.6]}}
{"location":{"type":"Point","coordinates":[29.3,30.7]}}
{"location":{"type":"Point","coordinates":[29.2,30.8]}}
{"location":{"type":"Point","coordinates":[29.1,30.9]}}
{"location":{"type":"Point","coordinates":[29.9,30.1]}}
Le seul point non retourné est [30,30]
qui est un point qui tombe sur le polygone et non à l’intérieur.
Si vous aviez voulu tous les points sur la limite du polygone et dans l’espace polygonal fermé, le $intercepts
L’opérateur QBE est fait pour vous.
// Find all intersecting a polygon,in this case a box
documents = await myCollection.find().filter({"location":{"$intersects":{"$geometry":{"type":"Polygon","coordinates":[[[29,30],[30,30],[30,31],[29,31],[29,30]]]}}}}).getDocuments();
contentOfDocs = documents.map(i => i.getContent());
console.log("Locations intersecting specified polygon")
contentOfDocs.forEach(logIt);
console.log();
Cette requête renvoie l’ensemble complet des points créés au début de cette discussion.
Locations intersecting specified polygon
{"location":{"type":"Point","coordinates":[29.8,30.2]}}
{"location":{"type":"Point","coordinates":[29.7,30.3]}}
{"location":{"type":"Point","coordinates":[29.6,30.4]}}
{"location":{"type":"Point","coordinates":[29.5,30.5]}}
{"location":{"type":"Point","coordinates":[29.4,30.6]}}
{"location":{"type":"Point","coordinates":[29.3,30.7]}}
{"location":{"type":"Point","coordinates":[29.2,30.8]}}
{"location":{"type":"Point","coordinates":[29.1,30.9]}}
{"location":{"type":"Point","coordinates":[30,30]}}
{"location":{"type":"Point","coordinates":[29.9,30.1]}}
Il s’agissait d’un aperçu rapide de l’utilisation des opérateurs spatiaux fournis par la base de données Oracle JSON. Ces opérateurs aident…