First basic implementation of search in map (not completely tested) (#18)

pull/108/head
Candid Dauth 2018-02-16 17:09:24 +01:00
rodzic 39364e839a
commit f61080c92c
10 zmienionych plików z 135 dodań i 19 usunięć

Wyświetl plik

@ -327,6 +327,16 @@ Search for places.
* _returns_ (Promise<string|Array<[searchResult](#searchresult)>>): If `data.query` is a URL to a GPX/KML/OSM/GeoJSON
file, that file as a string, otherwise an array of search results.
### `findOnMap(data)`
Search for markers and lines inside the map.
* `data` (object): An object with the following properties:
* `query` (string): The query string
* _returns_ (Promise<Array<[Marker](#marker-1)|[Line](#line-1)>>) An array of (stripped down) marker and line objects.
The objects only contain the `id`, `name`, `typeId`, ``lat`/`lon` (for markers), `left`/`top`/`right`/`bottom` (for
lines) properties, plus an additional `kind` property that is either `"marker"` or `"line"`.
### `getRoute(data)`
Calculate a route between two or more points.
@ -409,7 +419,7 @@ that line already has the right style.
Create a line.
* `data` ([line](#line)): The data of the line to create. An `id` will be assigned by the server
* `data` ([line](#line-1)): The data of the line to create. An `id` will be assigned by the server
* _returns_ (Promise<[line](#line-1)>): The line as it is on the server, with an `id` assigned and possibly its
styles modified by the settings of its type.
@ -504,7 +514,7 @@ A bounding box that describes which part of the map the user is currently viewin
* `id` (number): The ID of this marker
* `lat` (number, min: -90, max: 90): The latitude of this marker
* `lon` (number, min: -90, max: 90): The longitude of this marker
* `lon` (number, min: -180, max: 180): The longitude of this marker
* `name` (string): The name of this marker
* `colour` (string): The colour of this marker as a 6-digit hex value, for example `ff0000`
* `size` (number, min: 15): The height of the marker in pixels
@ -537,6 +547,7 @@ separately through `linePoints` events.
* `distance` (number): The distance of the line in kilometers (set by the server)
* `ascent`, `descent` (number): The total ascent/descent of the line in metres (set by the server)
* `time` (number): The time it takes to travel the route in seconds (only if routing mode is `car`, `bicycle` or `pedestrian`) (set by the server)
* `left`, `top`, `right`, `bottom` (number): The bounding box of the line (set by the server)
* `typeId` (number): The ID of the type of this line
* `data` (`{"key", "value"}`): The filled out form fields of the line
* `trackPoints`:

Wyświetl plik

@ -152,6 +152,10 @@ class Socket {
return this._emit("find", data);
}
findOnMap(data) {
return this._emit("findOnMap", data);
}
getRoute(data) {
return this._emit("getRoute", data);
}

Wyświetl plik

@ -51,5 +51,6 @@ require("./type")(Database);
require("./history")(Database);
require("./meta")(Database);
require("./route")(Database);
require("./search")(Database);
module.exports = Database;

Wyświetl plik

@ -42,7 +42,11 @@ module.exports = function(Database) {
distance : { type: Sequelize.FLOAT(24, 2).UNSIGNED, allowNull: true },
time : { type: Sequelize.INTEGER.UNSIGNED, allowNull: true },
ascent : { type: Sequelize.INTEGER.UNSIGNED, allowNull: true },
descent : { type: Sequelize.INTEGER.UNSIGNED, allowNull: true }
descent : { type: Sequelize.INTEGER.UNSIGNED, allowNull: true },
top: this._TYPES.lat,
bottom: this._TYPES.lat,
left: this._TYPES.lon,
right: this._TYPES.lon
});
this._conn.define("LinePoint", {
@ -278,14 +282,16 @@ module.exports = function(Database) {
});
},
_calculateRouting(line, trackPointsFromRoute) {
async _calculateRouting(line, trackPointsFromRoute) {
let trackPoints;
if(trackPointsFromRoute) {
line.distance = trackPointsFromRoute.distance;
line.time = trackPointsFromRoute.time;
line.ascent = trackPointsFromRoute.ascent;
line.descent = trackPointsFromRoute.descent;
return Promise.resolve(trackPointsFromRoute.trackPoints);
trackPoints = trackPointsFromRoute.trackPoints;
} else if(line.mode == "track" && line.trackPoints && line.trackPoints.length >= 2) {
line.distance = utils.calculateDistance(line.trackPoints);
line.time = null;
@ -295,27 +301,30 @@ module.exports = function(Database) {
for(var i=0; i<line.trackPoints.length; i++)
line.trackPoints[i].idx = i;
return Promise.resolve(line.trackPoints);
trackPoints = line.trackPoints;
} else if(line.routePoints && line.routePoints.length >= 2 && line.mode && line.mode != "track") {
return routing.calculateRouting(line.routePoints, line.mode).then((routeData) => {
line.distance = routeData.distance;
line.time = routeData.time;
line.ascent = routeData.ascent;
line.descent = routeData.descent;
for(var i=0; i<routeData.trackPoints.length; i++)
routeData.trackPoints[i].idx = i;
return routeData.trackPoints;
});
let routeData = await routing.calculateRouting(line.routePoints, line.mode);
line.distance = routeData.distance;
line.time = routeData.time;
line.ascent = routeData.ascent;
line.descent = routeData.descent;
for(var i=0; i<routeData.trackPoints.length; i++)
routeData.trackPoints[i].idx = i;
trackPoints = routeData.trackPoints;
} else {
line.distance = utils.calculateDistance(line.routePoints);
line.time = null;
var trackPoints = [ ];
trackPoints = [ ];
for(var i=0; i<line.routePoints.length; i++) {
trackPoints.push(utils.extend({ }, line.routePoints[i], { zoom: 1, idx: i }));
}
return Promise.resolve(trackPoints);
}
Object.assign(line, utils.calculateBbox(trackPoints));
return line;
}
});
};

Wyświetl plik

@ -213,7 +213,29 @@ module.exports = function(Database) {
});
return Promise.all([ renameColMigrations, changeColMigrations, addColMigrations, dropdownKeyMigration, elevationMigration, legendMigration ]);
// Calculate bounding box for lines
let bboxMigration = addColMigrations.then(async () => {
if(await this.getMeta("hasBboxes"))
return;
let LinePoint = this._conn.model("LinePoint");
for(let line of await this._conn.model("Line").findAll()) {
let bbox = await Promise.props({
top: LinePoint.min("lat", { where: { lineId: line.id } }),
bottom: LinePoint.max("lat", { where: { lineId: line.id } }),
left: LinePoint.min("lon", { where: { lineId: line.id } }),
right: LinePoint.min("lon", { where: { lineId: line.id } })
});
await this._updatePadObject("Line", line.padId, line.id, bbox, true);
}
await this.setMeta("hasBboxes", true);
});
return Promise.all([ renameColMigrations, changeColMigrations, addColMigrations, dropdownKeyMigration, elevationMigration, legendMigration, bboxMigration ]);
}
});
};

Wyświetl plik

@ -0,0 +1,34 @@
const Promise = require("bluebird");
const Sequelize = require("sequelize");
const similarity = require("string-similarity");
const utils = require("../utils");
const Op = Sequelize.Op;
module.exports = (Database) => {
Object.assign(Database.prototype, {
async search(padId, searchText) {
let objects = [].concat(...(await Promise.all([ "Marker", "Line" ].map(async (kind) => {
let objs = await this._conn.model(kind).findAll({
where: Sequelize.and(
{ padId },
Sequelize.where(Sequelize.fn("lower", Sequelize.col(`${kind}.name`)), {[Op.like]: `%${searchText.toLowerCase()}%`})
),
attributes: [ "name", "typeId" ].concat(kind == "Marker" ? [ "lat", "lon" ] : [ "top", "left", "bottom", "right" ])
});
return objs.map((obj) => (Object.assign(JSON.parse(JSON.stringify(obj)), {
kind: kind.toLowerCase(),
similarity: similarity.compareTwoStrings(searchText, obj.name)
})));
}))));
objects.sort((a, b) => (b.similarity - a.similarity));
return objects;
}
});
};

Wyświetl plik

@ -44,6 +44,7 @@
"sequelize": "^4.32.2",
"socket.io": "^2.0.4",
"stream-combiner": "^0.2.2",
"string-similarity": "^1.2.0",
"underscore": "^1.8.3",
"unzipper": "^0.8.11"
},

Wyświetl plik

@ -503,6 +503,16 @@ utils.extend(SocketConnection.prototype, {
});
},
async findOnMap(data) {
if(!utils.stripObject(data, { query: "string" }))
throw new Error("Invalid parameters.");
if(!this.padId)
throw new Error("No collaborative map opened.");
return this.database.search(this.padId, data.query);
},
getRoute: function(data) {
return Promise.resolve().then(() => {
if(!utils.stripObject(data, { destinations: [ { lat: "number", lon: "number" } ], mode: "string" }))

Wyświetl plik

@ -361,6 +361,23 @@ function interceptWriteStream(writeStream) {
});
}
function calculateBbox(trackPoints) {
let bbox = { top: null, left: null, right: null, bottom: null };
for(let trackPoint of trackPoints) {
if(bbox.top == null || trackPoint.lat > bbox.top)
bbox.top = trackPoint.lat;
if(bbox.bottom == null || trackPoint.lat < bbox.bottom)
bbox.bottom = trackPoint.lat;
if(bbox.left == null || trackPoint.lon < bbox.left)
bbox.left = trackPoint.lon;
if(bbox.right == null || trackPoints.lon > bbox.right)
bbox.right = trackPoint.lon;
}
return bbox;
}
module.exports = {
isInBbox : isInBbox,
filterStreamPromise : filterStreamPromise,
@ -376,5 +393,6 @@ module.exports = {
streamToArrayPromise: streamToArrayPromise,
promiseAuto: promiseAuto,
modifyFunction: modifyFunction,
interceptWriteStream: interceptWriteStream
interceptWriteStream: interceptWriteStream,
calculateBbox
};

Wyświetl plik

@ -3228,6 +3228,12 @@ stream-http@^2.7.2:
to-arraybuffer "^1.0.0"
xtend "^4.0.0"
string-similarity@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-1.2.0.tgz#d75153cb383846318b7a39a8d9292bb4db4e9c30"
dependencies:
lodash "^4.13.1"
string-width@^1.0.1, string-width@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"