突破界限Redis电子围栏的延伸(redis电子围栏范围)
Redis电子围栏是一种基于Redis实现的地理围栏服务,可以帮助我们在处理位置相关的数据时更加方便和高效地实现某些功能。例如,在处理移动设备的位置信息时,我们可以使用这个服务来判断设备是否进入或离开了某个区域,进而触发相应的业务逻辑。
然而,Redis电子围栏目前只支持圆形和矩形两种类型的围栏,如果需要实现更加复杂的围栏形状,比如多边形、线段等,就需要进行一些扩展和改进。本文将介绍一种基于Redis和其他技术实现的电子围栏的延伸方案,可以支持更多类型的围栏形状和更灵活的查询方式。
方案介绍
我们的方案基于Redis的zset数据类型,利用其有序集合的特性实现电子围栏服务。每个围栏形状都被抽象成了一个有序集合,在这个有序集合中,存储了一系列的地理数据点,这些点构成了一个闭合的多边形(或其他所需的形状),表示了围栏的边界。
例如,下面展示了一个五边形的围栏:
127.0.0.1:6379> ZRANGE polygon_fenc 0 -1 WITHSCORES
1) "POINT(120.13525 30.26715)"2) "0"
3) "POINT(120.13695 30.26703)"4) "0"
5) "POINT(120.13747 30.26655)"6) "0"
7) "POINT(120.13685 30.26588)"8) "0"
9) "POINT(120.13522 30.26600)"10) "0"
11) "POINT(120.13525 30.26715)"12) "0"
可以看到,这个有序集合中存储了六个地理数据点,前五个点构成了一个五边形,最后一个点是为了闭合多边形而添加的一个点。
为了判断一个位置点是否在围栏内,我们需要进行一些计算工作。具体来说,我们可以借助开源的GeoHash算法,将围栏边界中的每个点表示为一个GeoHash值,并将这些值都存储在一个bitmap中。当需要判断一个位置点是否在围栏内时,我们将其也表示为一个GeoHash值,并查询对应的bitmap,如果这个位置点对应的bitmap值为1,则说明该位置点在围栏内,否则在围栏外。
为了方便查询和维护,我们还可以使用其他技术来优化我们的方案。例如,可以使用周边搜索服务来快速查询某个位置点周围的围栏。我们可以将所有的围栏边界存储到一个搜索引擎中,当需要查询某个位置点周围的围栏时,只需要将这个位置点作为查询条件提交到搜索引擎即可。
以上就是我们的电子围栏的延伸方案,该方案可以支持更多类型的围栏形状和更灵活的查询方式,具有很高的效率和可拓展性。在实际应用中,该方案可以应用到车辆定位、电子地图、移动社交等领域。如果您对该方案感兴趣,可以参考下面的代码实现进行尝试。
代码实现
以下是我们的代码实现,我们使用了Node.js和Redis来实现电子围栏的延伸服务:
var redis = require('redis');
var geohash = require('ngeohash');var async = require('async');
var turf = require('@turf/turf');var elasticsearch = require('elasticsearch');
// 初始化redis连接var client = redis.createClient({
host: '127.0.0.1', port: '6379'
});
// 初始化elasticsearch连接var esClient = new elasticsearch.Client({
host: '127.0.0.1:9200', log: 'trace'
});
// 围栏类型var FENCE_TYPE = {
POLYGON: 'polygon', LINESTRING: 'linestring'
}
/** * 创建围栏
* @param {*} fenceId 围栏ID * @param {*} fenceType 围栏类型
* @param {*} coords 围栏坐标点 * @param {*} callback 回调函数
*/function createFence(fenceId, fenceType, coords, callback) {
// 构建围栏对象 var fence = {
type: fenceType, coords: coords
};
// 将围栏对象序列化为json字符串 var fenceStr = JSON.stringify(fence);
// 存储围栏对象
client.set(fenceId, fenceStr, function(err, reply) { callback(err);
});}
/** * 删除围栏
* @param {*} fenceId 围栏ID * @param {*} callback 回调函数
*/function deleteFence(fenceId, callback) {
// 删除围栏对象 client.del(fenceId, function(err, reply) {
callback(err); });
}
/** * 查询围栏
* @param {*} fenceId 围栏ID * @param {*} callback 回调函数
*/function queryFence(fenceId, callback) {
// 查询围栏对象 client.get(fenceId, function(err, reply) {
if (err) { return callback(err);
}
// 解析围栏对象 var fence = JSON.parse(reply.toString());
// 查询围栏边界中的所有点
var coords = fence.coords;
// 将边界点解析为GeoHash值 var geoHashValues = coords.map(function(coord) {
var geoHash = geohash.encode(coord[1], coord[0], 6); return geoHash;
});
// 查询对应的bitmap var bitmapKeys = geoHashValues.map(function(geoHash) {
return 'fence_bitmap:' + geoHash; });
client.bitcount(bitmapKeys, function(err, count) {
if (err) { return callback(err);
}
var isInside = (count % 2 == 1); // 奇数个点在多边形内部,偶数个点在多边形外部 callback(null, isInside);
}); });
}
/** * 查询某个点周围的围栏
* @param {*} lat 纬度 * @param {*} lng 经度
* @param {*} radius 搜索范围(千米) * @param {*} callback 回调函数
*/function queryNearbyFences(lat, lng, radius, callback) {
// 周边搜索配置 var searchParams = {
index: 'fences', type: 'fence',
body: { query: {
bool: { must: {
match_all: {} },
filter: { geo_distance: {
distance: radius + 'km', location: {
lat: lat, lon: lng
} }
} }
} }
};
// 进行搜索 esClient.search(searchParams, function(err, response) {
if (err) { return callback(err);
}
var fences = response.hits.hits.map(function(hit) { return hit._source;
});
callback(null, fences); });
}
/** * 初始化搜索引擎
*/function initSearchEngine() {
// 创建索引 esClient.indices.create({
index: 'fences' }, function(err, resp, status) {
if (err) { console.log(err);
} else { console.log("Index created!", resp);
} });
// 映射类型和字段
var mapping = { fence: {
properties: { location: {
type: "geo_point" }
} }
};
// 更新映射 esClient.indices.putMapping({
index: "fences",