У меня есть R data.frame, содержащий долготу, широту, которая охватывает всю карту США. Когда X число записей находится в пределах небольшого географического региона, скажем, на несколько градусов долготы и на несколько градусов, я хочу, чтобы это удалось обнаружить, а затем моя программа вернет координаты для географического ограничивающего прямоугольника. Есть ли Python или R CRAN-пакет, который уже делает это? Если нет, как бы мне узнать, как это узнать?
Обнаружение географических кластеров
Ответ 1
Мне удалось совместить Joran-ответ с комментарием Dan H. Это пример вывода:
Код python испускает функции для R: map() и rect(). Эта карта примера США была создана с помощью:
map('state', plot = TRUE, fill = FALSE, col = palette())
а затем вы можете применить rect() соответственно с помощью в интерпретаторе R GUI (см. ниже).
import math
from collections import defaultdict
to_rad = math.pi / 180.0 # convert lat or lng to radians
fname = "site.tsv" # file format: LAT\tLONG
threshhold_dist=50 # adjust to your needs
threshhold_locations=15 # minimum # of locations needed in a cluster
def dist(lat1,lng1,lat2,lng2):
global to_rad
earth_radius_km = 6371
dLat = (lat2-lat1) * to_rad
dLon = (lng2-lng1) * to_rad
lat1_rad = lat1 * to_rad
lat2_rad = lat2 * to_rad
a = math.sin(dLat/2) * math.sin(dLat/2) + math.sin(dLon/2) * math.sin(dLon/2) * math.cos(lat1_rad) * math.cos(lat2_rad)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a));
dist = earth_radius_km * c
return dist
def bounding_box(src, neighbors):
neighbors.append(src)
# nw = NorthWest se=SouthEast
nw_lat = -360
nw_lng = 360
se_lat = 360
se_lng = -360
for (y,x) in neighbors:
if y > nw_lat: nw_lat = y
if x > se_lng: se_lng = x
if y < se_lat: se_lat = y
if x < nw_lng: nw_lng = x
# add some padding
pad = 0.5
nw_lat += pad
nw_lng -= pad
se_lat -= pad
se_lng += pad
# sutiable for r map() function
return (se_lat,nw_lat,nw_lng,se_lng)
def sitesDist(site1,site2):
#just a helper to shorted list comprehension below
return dist(site1[0],site1[1], site2[0], site2[1])
def load_site_data():
global fname
sites = defaultdict(tuple)
data = open(fname,encoding="latin-1")
data.readline() # skip header
for line in data:
line = line[:-1]
slots = line.split("\t")
lat = float(slots[0])
lng = float(slots[1])
lat_rad = lat * math.pi / 180.0
lng_rad = lng * math.pi / 180.0
sites[(lat,lng)] = (lat,lng) #(lat_rad,lng_rad)
return sites
def main():
sites_dict = {}
sites = load_site_data()
for site in sites:
#for each site put it in a dictionary with its value being an array of neighbors
sites_dict[site] = [x for x in sites if x != site and sitesDist(site,x) < threshhold_dist]
results = {}
for site in sites:
j = len(sites_dict[site])
if j >= threshhold_locations:
coord = bounding_box( site, sites_dict[site] )
results[coord] = coord
for bbox in results:
yx="ylim=c(%s,%s), xlim=c(%s,%s)" % (results[bbox]) #(se_lat,nw_lat,nw_lng,se_lng)
print('map("county", plot=T, fill=T, col=palette(), %s)' % yx)
rect='rect(%s,%s, %s,%s, col=c("red"))' % (results[bbox][2], results[bbox][0], results[bbox][3], results[bbox][2])
print(rect)
print("")
main()
Вот пример файла TSV (site.tsv)
LAT LONG
36.3312 -94.1334
36.6828 -121.791
37.2307 -121.96
37.3857 -122.026
37.3857 -122.026
37.3857 -122.026
37.3895 -97.644
37.3992 -122.139
37.3992 -122.139
37.402 -122.078
37.402 -122.078
37.402 -122.078
37.402 -122.078
37.402 -122.078
37.48 -122.144
37.48 -122.144
37.55 126.967
С моим набором данных выводится вывод моего python script, показанный на карте США. Я изменил цвета для ясности.
rect(-74.989,39.7667, -73.0419,41.5209, col=c("red"))
rect(-123.005,36.8144, -121.392,38.3672, col=c("green"))
rect(-78.2422,38.2474, -76.3,39.9282, col=c("blue"))
Дополнение к 2013-05-01 для Yacob
Эти две строки дают вам всю цель...
map("county", plot=T )
rect(-122.644,36.7307, -121.46,37.98, col=c("red"))
Если вы хотите ограничить часть карты, вы можете использовать ylim
и xlim
map("county", plot=T, ylim=c(36.7307,37.98), xlim=c(-122.644,-121.46))
# or for more coloring, but choose one or the other map("country") commands
map("county", plot=T, fill=T, col=palette(), ylim=c(36.7307,37.98), xlim=c(-122.644,-121.46))
rect(-122.644,36.7307, -121.46,37.98, col=c("red"))
Вы хотите использовать карту мира...
map("world", plot=T )
Прошло много времени с тех пор, как я использовал этот код python, который я разместил ниже, поэтому я постараюсь помочь вам.
threshhold_dist is the size of the bounding box, ie: the geographical area
theshhold_location is the number of lat/lng points needed with in
the bounding box in order for it to be considered a cluster.
Вот полный пример. Файл TSV находится на сайте pastebin.com. Я также включил изображение, сгенерированное из R, которое содержит вывод всех команд rect().
# pyclusters.py
# May-02-2013
# -John Taylor
# latlng.tsv is located at http://pastebin.com/cyvEdx3V
# use the "RAW Paste Data" to preserve the tab characters
import math
from collections import defaultdict
# See also: http://www.geomidpoint.com/example.html
# See also: http://www.movable-type.co.uk/scripts/latlong.html
to_rad = math.pi / 180.0 # convert lat or lng to radians
fname = "latlng.tsv" # file format: LAT\tLONG
threshhold_dist=20 # adjust to your needs
threshhold_locations=20 # minimum # of locations needed in a cluster
earth_radius_km = 6371
def coord2cart(lat,lng):
x = math.cos(lat) * math.cos(lng)
y = math.cos(lat) * math.sin(lng)
z = math.sin(lat)
return (x,y,z)
def cart2corrd(x,y,z):
lon = math.atan2(y,x)
hyp = math.sqrt(x*x + y*y)
lat = math.atan2(z,hyp)
return(lat,lng)
def dist(lat1,lng1,lat2,lng2):
global to_rad, earth_radius_km
dLat = (lat2-lat1) * to_rad
dLon = (lng2-lng1) * to_rad
lat1_rad = lat1 * to_rad
lat2_rad = lat2 * to_rad
a = math.sin(dLat/2) * math.sin(dLat/2) + math.sin(dLon/2) * math.sin(dLon/2) * math.cos(lat1_rad) * math.cos(lat2_rad)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a));
dist = earth_radius_km * c
return dist
def bounding_box(src, neighbors):
neighbors.append(src)
# nw = NorthWest se=SouthEast
nw_lat = -360
nw_lng = 360
se_lat = 360
se_lng = -360
for (y,x) in neighbors:
if y > nw_lat: nw_lat = y
if x > se_lng: se_lng = x
if y < se_lat: se_lat = y
if x < nw_lng: nw_lng = x
# add some padding
pad = 0.5
nw_lat += pad
nw_lng -= pad
se_lat -= pad
se_lng += pad
#print("answer:")
#print("nw lat,lng : %s %s" % (nw_lat,nw_lng))
#print("se lat,lng : %s %s" % (se_lat,se_lng))
# sutiable for r map() function
return (se_lat,nw_lat,nw_lng,se_lng)
def sitesDist(site1,site2):
# just a helper to shorted list comprehensioin below
return dist(site1[0],site1[1], site2[0], site2[1])
def load_site_data():
global fname
sites = defaultdict(tuple)
data = open(fname,encoding="latin-1")
data.readline() # skip header
for line in data:
line = line[:-1]
slots = line.split("\t")
lat = float(slots[0])
lng = float(slots[1])
lat_rad = lat * math.pi / 180.0
lng_rad = lng * math.pi / 180.0
sites[(lat,lng)] = (lat,lng) #(lat_rad,lng_rad)
return sites
def main():
color_list = ( "red", "blue", "green", "yellow", "orange", "brown", "pink", "purple" )
color_idx = 0
sites_dict = {}
sites = load_site_data()
for site in sites:
#for each site put it in a dictionarry with its value being an array of neighbors
sites_dict[site] = [x for x in sites if x != site and sitesDist(site,x) < threshhold_dist]
print("")
print('map("state", plot=T)') # or use: county instead of state
print("")
results = {}
for site in sites:
j = len(sites_dict[site])
if j >= threshhold_locations:
coord = bounding_box( site, sites_dict[site] )
results[coord] = coord
for bbox in results:
yx="ylim=c(%s,%s), xlim=c(%s,%s)" % (results[bbox]) #(se_lat,nw_lat,nw_lng,se_lng)
# important!
# if you want an individual map for each cluster, uncomment this line
#print('map("county", plot=T, fill=T, col=palette(), %s)' % yx)
if len(color_list) == color_idx:
color_idx = 0
rect='rect(%s,%s, %s,%s, col=c("%s"))' % (results[bbox][2], results[bbox][0], results[bbox][3], results[bbox][1], color_list[color_idx])
color_idx += 1
print(rect)
print("")
main()
Ответ 2
Я делаю это на регулярной основе, сначала создавая матрицу расстояний, а затем запуская кластеризацию на ней. Вот мой код.
library(geosphere)
library(cluster)
clusteramounts <- 10
distance.matrix <- (distm(points.to.group[,c("lon","lat")]))
clustersx <- as.hclust(agnes(distance.matrix, diss = T))
points.to.group$group <- cutree(clustersx, k=clusteramounts)
Я не уверен, полностью ли он решает вашу проблему. Вы можете протестировать с разными k, а также, возможно, сделать второй запуск кластеризации некоторых из первых кластеров в случае, если они слишком большие, например, если у вас есть одна точка в Миннесоте и тысяча в Калифорнии. Когда у вас есть point.to.group $group, вы можете получить ограничивающие поля, набрав max и min lat lon для каждой группы.
Если вы хотите, чтобы X было 20, и у вас есть 18 очков в Нью-Йорке и 22 в Далласе, вы должны решить, хотите ли вы одну маленькую и одну действительно большую коробку (по 20 очков каждый), если лучше иметь поле Далласа включает 22 очка, или если вы хотите разбить 22 балла в Далласе на две группы. Кластеризация на основе расстояния может быть хорошей в некоторых из этих случаев. Но это, конечно, зависит от того, почему вы хотите группировать очки.
/Chris
Ответ 3
Несколько идей:
- Ad-hoc и примерный: "2-D гистограмма". Создайте произвольные "прямоугольные" ящики ширины по вашему выбору, присвойте каждому бин ID. Размещение точки в бункере означает "связывать точку с идентификатором ячейки". После каждого добавления в корзину задайте ячейке, сколько у нее очков. Даунсайд: неверно "видит" кластер точек, которые прокладывают границу бункера; и: бункеры "постоянной продольной ширины" фактически (пространственно) меньше при движении на север.
- Используйте библиотеку "Shapely" для Python. Следуйте этому примеру с запасом для "точек буферизации" и выполните каскадное объединение буферов. Ищите глобусы над определенной областью или "содержат" определенное количество исходных точек. Обратите внимание, что Shapely не является по своей сути "геосъемкой", поэтому вам придется добавлять исправления, если они вам нужны.
- Используйте истинную БД с пространственной обработкой. MySQL, Oracle, Postgres (с PostGIS), MSSQL все (я думаю) имеют типы данных "Геометрия" и "География", и вы можете делать пространственные запросы на них (из ваших сценариев Python).
Каждый из них имеет разные затраты в долларах и времени (в кривой обучения)... и в разной степени геопространственной точности. Вы должны выбрать то, что подходит вашему бюджету и/или требованиям.
Ответ 4
Если вы используете стройное, вы можете расширить функцию cluster_points для возврата ограничивающей рамки кластера с помощью свойства .bounds в форме упорядоченной геометрии, например:
clusterlist.append(cluster, (poly.buffer(-b)).bounds)
Ответ 5
может быть что-то вроде
def dist(lat1,lon1,lat2,lon2):
#just return normal x,y dist
return sqrt((lat1-lat2)**2+(lon1-lon2)**2)
def sitesDist(site1,site2):
#just a helper to shorted list comprehensioin below
return dist(site1.lat,site1.lon,site2.lat,site2.lon)
sites_dict = {}
threshhold_dist=5 #example dist
for site in sites:
#for each site put it in a dictionarry with its value being an array of neighbors
sites_dict[site] = [x for x in sites if x != site and sitesDist(site,x) < threshhold_dist]
print "\n".join(sites_dict)