我正在开发一个Node.js网络应用程序,它将允许用户搜索存储在后端PostgreSQL数据库中的世界上每所学校(约700万)。
UX
用户将在Google地图上选择一个位置(带有可选字段,例如学校类型,N个要显示的学校数,M半径(以公里为单位)),并且地图将显示M公里以内的前N个学校。用户选择的位置可能是有效地址,也可能不是有效地址,因此Google Maps会将用户选择的位置转换为纬度和经度,我的Web应用程序将调用function findSchoolsByLocation(latitude, longitude, filterParams...)
并从PostgreSQL返回数据的JSON对象
数据
我得到的原始数据包括该学校的地址和元数据,如下所示:
| Primary Key | Address -------------------------------- | School Name ------- |
| ??????????? | 3210 Wimberly Rd, Amarillo TX 79109-3433 | University of Texas |
| ??????????? | 5198 Jex St, Arlington, TX, 78019-4532 | Texas Elementary School |
在验证地址和元数据之后,最好是1)对所有700万个地址进行地理编码,因为它们已存储在PostgreSQL中,并使用纬度和经度作为主键;或者2)使用地址作为主键,而findSchoolsByLocation
是否能够以字符串地址找到最近的N个地址,而没有纬度和经度?
如果为1),我正在考虑在本地服务器中使用PostGIS(更改最少的代码),在AWS RDS Postgre中使用PostGIS以更好地扩展(我对AWS不熟悉),或使用Google Geocode API(更准确,但是是Web服务)。我需要对大量地址进行地址解析,但只需要执行一次,随后的更改就只会更新地址更新后的地址地理编码(显然不会那么多)。我已经了解了使用Web服务与直接写入数据库相比的优点和缺点。对于我的用例来说,哪个更好的选择?
在此处查找段落答复,我想写一份报告来解释我的决策过程,备选方案以及处理实施此Web应用程序,地理编码和数据库设计的风险和错误:
答案 0 :(得分:0)
假定数据以所述格式提供:
| Primary Key | Address -------------------------------- | School Name ------- |
| ??????????? | 3210 Wimberly Rd, Amarillo TX 79109-3433 | University of Texas |
| ??????????? | 5198 Jex St, Arlington, TX, 78019-4532 | Texas Elementary School |
我该怎么办?
我认为以下解决方案会很好地工作:
像这样
| Primary Key | Address -------------------------------- | School Name ------- | Latitude | Longitude |
| ??????????? | 3210 Wimberly Rd, Amarillo TX 79109-3433 | University of Texas | ??????????? | ??????????? |
| ??????????? | 5198 Jex St, Arlington, TX, 78019-4532 | Texas Elementary School | ??????????? | ??????????? |
GEO_ADDRESS
以便将来使用,也可以将手动修复推广到通用算法中,而无需用户干预所使用的地理编码系统即可正常工作。R
内的学校,您可以找到经纬度的地理位置。使用经度和纬度的“地址”表示例 列将#LONGITUDE#,#LATITUDE#和#DISTANCE_IN_MILES#替换为 您的搜索值
SELECT addresses.*, (ACOS( SIN(RADIANS(#LATITUDE#)) * SIN(RADIANS(addresses.latitude)) + COS(RADIANS(#LATITUDE#)) * COS(RADIANS(addresses.latitude)) * COS(RADIANS(addresses.longitude) - RADIANS(#LONGITUDE#)) ) * 3963.1676) AS distance
FROM addresses
WHERE (((ACOS( SIN(RADIANS(#LATITUDE#)) * SIN(RADIANS(addresses.latitude)) + COS(RADIANS(#LATITUDE#)) * COS(RADIANS(addresses.latitude)) * COS(RADIANS(addresses.longitude) - RADIANS(#LONGITUDE#)) ) * 3963.1676) <= #DISTANCE#) OR (addresses.latitude = #LATITUDE# AND addresses.longitude = #LONGITUDE#))
在任何一种情况下,当范围跨过-180/180(围绕时线的经度)或-90/90(围绕两极的纬度)时,您可能都必须处理(取决于地址的受支持区域)例如,将范围分为discontinuity之前和之后。您不太可能需要支持这些领域。
这应该为您提供准确的选择,或者如果您希望使用更快的查询(例如SELECT * FROM Table WHERE Latitude > latitude_min AND Latitude > latitude_max AND Longitude > longitude_min AND Longitude < longitude_max
),至少可以给您留下足够少的选项以按实际距离而不是曼哈顿距离进行过滤。如果没有,您可以安全地向用户显示“选择显示的学校太多”或“缩小搜索范围”,但这必须添加到需求说明中。
如果我在这个决定中犯了一个错误,该怎么办?
您可能必须根据其他或明确的要求来开发新的解决方案。这就是迭代软件开发过程的本质。您越早交付一些东西,您就越早失败并进入下一个迭代,因此最简单的解决方案是一个很好的开始,而原型是与客户确认需求的宝贵方法。
这将如何计算冒险?
步骤越小,风险越小。经常进行原型制作,您将避免大的风险。例如,要评估我上面建议的两个解决方案的性能影响(使用简单的SQL查询根据曼哈顿距离进行选择,而使用复杂的SQL查询根据实际距离进行选择),则可以创建一个没有真实数据的简单测试并验证每个解决方案的性能影响解决方案。
在确定哪种解决方案更好时,我将如何处理与队友的冲突?
通过提出替代方案,开放讨论并同意最佳选择。如果由于某种原因,这无法解决问题,请升级讨论范围以包括您的管理层。
答案 1 :(得分:0)
一些注意事项:
没有摆脱地理编码的选择(不可能进行字符串查找)
您确定Google地理编码有用吗?它只是一个地理编码工具,正如有人提到的那样,它们不允许保留地理编码数据。您可能需要使用其他一些服务(mapquest似乎有存储结果的计划)
回答您的3个问题-我认为您的主要风险和担忧将是价格。只需对您可能正在使用的所有服务进行成本分析,我认为之后对您来说将很清楚。首先,我将检查cartodb(或类似的东西)是否为您提供了解决方案。如果不是,则研究哪种地理编码提供者适合您(关键是能够存储您获取的数据)。然后从AWS获得估算值。我认为运行本地数据库可能很麻烦,但可能会节省成本。
关于技术部分,我认为您应该使用空间类型/索引,而无需使用公式来计算距离。下面是一个有关如何创建,查询和检索空间数据的简单示例(以防您不熟悉或模糊不清)
--- set up postgis environment with docker if needed
--- (from here: https://alexurquhart.com/post/set-up-postgis-with-docker):
-- docker volume create pg_data
-- docker run --name=postgis -d -e POSTGRES_USER=alex -e POSTGRES_PASS=password -e POSTGRES_DBNAME=gis -e ALLOW_IP_RANGE=0.0.0.0/0 -p 5432:5432 -v pg_data:/var/lib/postgresql --restart=always kartoza/postgis:9.6-2.4
-- drop table schools
create table schools (
country varchar(20),
state varchar(20),
school varchar(60),
lat float,
long float,
loc GEOGRAPHY
);
---- NYC schools
insert into schools values ('USA', 'NY', 'New York City School District 1', 40.7212744,-73.986311, null);
insert into schools values ('USA', 'NY', 'KIPP NYC College Prep', 40.8162614,-73.9260793, null);
insert into schools values ('USA', 'NY', 'The Young Womens Leadership School of Astoria', 40.7712631,-73.9241695, null);
insert into schools values ('USA', 'NY', 'Brooklyn East Collegiate Charter School', 40.6784249,-73.9658189, null);
insert into schools values ('USA', 'NY', 'N Y City Board of Education', 40.6933457,-73.9215088, null);
insert into schools values ('USA', 'NY', 'New York City School District 28', 40.7027487,-73.8079333, null);
insert into schools values ('USA', 'NY', 'School of Math, Science, and Healthy', 40.6394884,-74.0202785, null);
UPDATE schools SET loc = ST_POINT(long,lat);
CREATE INDEX school_loc ON schools USING GIST (loc);
--- get schools within 10km around (-73.9091706, 40.71163)
select S.*
,ST_Distance(loc, ST_POINT(-73.9091706, 40.71163)) as dist
from schools S
where ST_Distance(loc, ST_POINT(-73.9091706, 40.71163)) < 10000
---- Converting result to JSON.
---- It's a good idea to get it as GeoJSON since it's supported almost by any spatial tool. You can use http://geojson.io to visualize it
with result as (
select S.*, ST_Distance(loc, ST_POINT(-73.9091706, 40.71163)) as dist from schools S
)
,features as (
select json_build_object(
'type', 'Feature',
'geometry', st_AsGeoJSON(loc)::json,
'properties', (school, dist)
) AS feature
from result
where dist < 10000
order by dist
)
------ main
-- select feature from features
select json_build_object(
'type', 'FeatureCollection',
'features', json_agg(feature)
)
from features