Given two location points (Latitude, Longitude) in GEOSPHERE library of PYMONGO, how to calculate distance between these two points? - pymongo

from pymongo import GEOSPHERE
from pymongo import MongoClient
CONNECTION_STRING = "mongodb://localhost:27017"
client = pymongo.MongoClient(CONNECTION_STRING)
db = client['Chandigarh']
collection = db['LocationPoints']
db.collection.createIndex({"location":"2d"})
point1 = {'location': {'type': "Point", 'coordinates': [30.477666, -76.452333]}}
point2 = {'location': {'type': "Point", 'coordinates': [30.458333, -76.4103333]}}
collection.insert_one(point1)
collection.insert_one(point2)
###
Querying database for distance between these two points
or
Calculating distance between these two points in meters
###
I cannot see presence of such functionality in the documentation and otherwise, I am unaware of how to calculate distance between two location points.

Related

Assign Linestring to Polygon based on Max length

I have two geopandas dataframes one is Linestring and other is Polygon. I need to assign the Linestring to Polygon based on Max length. The plot of them looks below.The two polygons are separated by edge color which is Balck.
I am using the following code to assign Linestring to Polygon
well_segments = gpd.overlay(Polygons,Linestring, how='intersection')
well_segments['segment_length'] = well_segments.length
well_segments["geometry"] = well_segments.geometry.to_wkt()
well_segments_df = spark.createDataFrame(well_segments)
windowSpec = Window.partitionBy("api12").orderBy(col("segment_length").desc())
well_segments_valid_df = well_segments_df.select("API", "ID", f.row_number().over(windowSpec).alias("rn"), "segment_length", "geometry").filter(f.col("rn") == 1)
Is there any most efiicient way of doing it in Geopandas or Pandas
you have not provided any sample data. So have used some polygons from natural earth dataset and generated 5 lines which will be of different lengths in each to these polygons
the actual solution is:
use sjoin() instead of overlay()
filter down line with greatest length for each polygon (index_right)
longest = (
gpd.sjoin(linestrings, polygons, predicate="intersects")
.assign(len=lambda d: d["geometry"].length)
.sort_values(["index_right", "len"])
.groupby("index_right")["geometry"]
.last()
)
### full working code ###
import geopandas as gpd
from shapely.geometry import LineString
import numpy as np
import folium
import warnings
r = np.random.RandomState(22)
polygons = (
gpd.read_file(gpd.datasets.get_path("naturalearth_lowres"))
.loc[lambda d: d["geometry"].type.eq("Polygon") & d["continent"].eq("Africa")]
.sample(5, random_state=r)
.loc[:, ["geometry"]]
)
lss = (
polygons.exterior.apply(
lambda g: np.array(g.coords)[r.choice(len(g.coords), [5, 2])]
)
.explode()
.apply(LineString)
)
linestrings = gpd.GeoDataFrame(geometry=lss, crs=polygons.crs).reset_index(drop=True)
# find the longest line in each polygon
with warnings.catch_warnings():
warnings.simplefilter("ignore")
longest = (
gpd.sjoin(linestrings, polygons, predicate="intersects")
.assign(len=lambda d: d["geometry"].length)
.sort_values(["index_right", "len"])
.groupby("index_right")["geometry"]
.last()
)
longest = gpd.GeoSeries(longest, crs=polygons.crs)
# visualise it...
m = polygons.explore(height=300, width=600, color="cyan", name="polys")
m = linestrings.explore(m=m, name="all lines", color="blue", style_kwds={"weight":.8})
m = longest.explore(m=m, name="longest", color="red")
folium.LayerControl().add_to(m)
m

How to plot a map of a semi-sphere (eg northern hemisphere) using matplotlib cartopy

How to plot a map of a semi-sphere (eg northern hemisphere) using cartopy.
I'm trying to plot a map of the northern hemisphere using cartopy. But I don't understand how should I define the extent of the map so that only this region of interest is plotted. I would like the map to be cut off at 0° latitude. I would like to have code where I could easily define any subset of the glob using the ccrs.NearsidePerspective projection, or the ccrs.Orthographic projection.
Below I leave a code for reproduction.
import numpy as np
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
# Creating fake data
x = np.linspace(-180, 180, 361)
y = np.linspace(-90, 90, 181)
lon, lat = np.meshgrid(x, y)
values = np.random.random(lon.shape)*20
fig = plt.figure(figsize=(15, 10))
proj = ccrs.NearsidePerspective(central_longitude=-45, central_latitude=21)
ax = fig.add_subplot(121, projection=proj)
ax.set_extent([-120, 40, 0, 60])
ax.pcolormesh(lon, lat, values, transform=ccrs.PlateCarree())
ax.coastlines(linewidth=2)
gl = ax.gridlines(draw_labels=True, linestyle='--')
The code generates the following figure:
Thank you very much in advance.
Robson
To plot only the upper hemisphere part of the map projection, a polygon of that part is needed to use as the projection boundary.
That polygon is created as a matplotlib-path object. It vertices' coordinates are data coordinates in my code, so that, no transformation is required when applied to the final plot.
This is a complete code:-
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import matplotlib.path as mpath
import numpy as np
from geographiclib.geodesic import Geodesic
fig = plt.figure(figsize=[12, 12])
proj = ccrs.NearsidePerspective(central_longitude=-45, central_latitude=21, satellite_height=35785831)
ax = plt.subplot(projection=proj)
# The value of r is obtained by previous run of this code ...
# with the line .. #print(ax.get_xlim()) uncommented
r = 5476336.098
ax.set_xlim(-r, r)
ax.set_ylim(-r, r)
ax.stock_img()
ax.coastlines(lw=1, color="darkblue")
# Find the locations of points along the equatorial arc
# start location
lon_fr, lat_fr = 30, 0
# end location
lon_to, lat_to = -120, 0
# This gets geodesic between the two points, WGS84 ellipsoid is used
geodl = Geodesic.WGS84.InverseLine(lat_fr, lon_fr, lat_to, lon_to)
lonlist, latlist = [], []
num_points = 32 #for series of points on geodesic/equator
for ea in np.linspace(0, geodl.s13, num_points):
g = geodl.Position(ea, Geodesic.STANDARD | Geodesic.LONG_UNROLL)
#print("{:.0f} {:.5f} {:.5f} {:.5f}".format(g['s12'], g['lat2'], g['lon2'], g['azi2']))
lon2, lat2 = g['lon2'], g['lat2']
lonlist.append( g['lon2'] )
latlist.append( g['lat2'] )
# Get data-coords from (lonlist, latlist)
# .. as points along equatorial arc
dataxy = proj.transform_points(ccrs.PlateCarree(), np.array(lonlist), np.array(latlist))
# (Uncomment to) Plot equator line
#ax.plot(dataxy[:, 0:1], dataxy[:, 1:2], "go-", linewidth=2, markersize=5, zorder=10)
# Top semi-circle arc for map extent
theta = np.linspace(-0.5*np.pi, 0.5*np.pi, 64)
center, radius = [0, 0], r
verts = np.vstack([np.sin(theta), np.cos(theta)]).T
# Combine vertices of the semi-circle and equatorial arcs
# These points are in data coordinates, ready to plot on the axes.
verts = np.vstack([verts*r, dataxy[:, 0:2]])
polygon = mpath.Path(verts + center)
ax.set_boundary(polygon) #This masks-out unwanted part of the plot
gl = ax.gridlines(draw_labels=True, xlocs=range(-150,180,30), ylocs=range(0, 90, 15),
y_inline=True, linestyle='--', lw= 5, color= "w", )
# Get limits, the values are the radius of the circular map extent
# The values is then used as r = 5476336.09797 on top of the code
#print(ax.get_xlim())
#print(ax.get_ylim())
plt.show()

Merge countries using Cartopy

I am using the following code to make a map for Sweden, Norway and Finland together as one area. however, I am struggling with it. I'm following this example, Python Mapping in Matplotlib Cartopy Color One Country.
from shapely.geometry import Polygon
from cartopy.io import shapereader
import cartopy.io.img_tiles as cimgt
import cartopy.crs as ccrs
import geopandas
import matplotlib.pyplot as plt
def rect_from_bound(xmin, xmax, ymin, ymax):
"""Returns list of (x,y)'s for a rectangle"""
xs = [xmax, xmin, xmin, xmax, xmax]
ys = [ymax, ymax, ymin, ymin, ymax]
return [(x, y) for x, y in zip(xs, ys)]
# request data for use by geopandas
resolution = '10m'
category = 'cultural'
name = 'admin_0_countries'
countries = ['Norway', 'Sweden', 'Finland']
shpfilename = shapereader.natural_earth(resolution, category, name)
df = geopandas.read_file(shpfilename)
extent = [2, 32, 55, 72]
# get geometry of a country
for country in (countries):
poly = [df.loc[df['ADMIN'] == country]['geometry'].values[0]]
stamen_terrain = cimgt.StamenTerrain()
# projections that involved
st_proj = stamen_terrain.crs #projection used by Stamen images
ll_proj = ccrs.PlateCarree() #CRS for raw long/lat
# create fig and axes using intended projection
fig = plt.figure(figsize=(8,9))
ax = fig.add_subplot(122, projection=st_proj)
ax.add_geometries(poly, crs=ll_proj, facecolor='none', edgecolor='black')
pad1 = 0.5 #padding, degrees unit
exts = [poly[0].bounds[0] - pad1, poly[0].bounds[2] + pad1, poly[0].bounds[1] - pad1, poly[0].bounds[3] + pad1];
ax.set_extent(exts, crs=ll_proj)
# make a mask polygon by polygon's difference operation
# base polygon is a rectangle, another polygon is simplified switzerland
msk = Polygon(rect_from_bound(*exts)).difference( poly[0].simplify(0.01) )
msk_stm = st_proj.project_geometry (msk, ll_proj) # project geometry to the projection used by stamen
# get and plot Stamen images
ax.add_image(stamen_terrain, 8) # this requests image, and plot
# plot the mask using semi-transparency (alpha=0.65) on the masked-out portion
ax.add_geometries( msk_stm, st_proj, zorder=12, facecolor='white', edgecolor='none', alpha=0.65)
ax.gridlines(draw_labels=True)
plt.show()
What I have is separated maps. THoguh I need only one map of them.
Can you please help?
Thank you.
The code here that you adapted to your work is good for a single country. If multiple contiguous countries are new target, one need to select all of them and dissolve into a single geometry. Only a few lines of code need to be modified.
Example: new target countries: ['Norway','Sweden', 'Finland']
The line of code that need to be replaced:
poly = [df.loc[df['ADMIN'] == 'Switzerland']['geometry'].values[0]]
Replace it with these lines of code:
scan3 = df[ df['ADMIN'].isin(['Norway','Sweden', 'Finland']) ]
scan3_dissolved = scan3.dissolve(by='LEVEL')
poly = [scan3_dissolved['geometry'].values[0]]
And you should get a plot similar to this:

Plotting Lat/Long Points Using Basemap

I am trying to plot points on a map using matplotlib and Basemap, where the points represent the lat/long for specific buildings. My map does indeed plot the points, but puts them in the wrong location. When I use the same data and do the same thing using Bokeh, instead of matplotlib and basemap, I get the correct plot.
Here is the CORRECT result in Bokeh:
Bokeh Version
And here is the INCORRECT result in Basemap:
Basemap Version
I have seen discussion elsewhere on StackOverflow that suggested this might be related to the fact that plot() "shifts" the longitude somehow. I've tried the suggestion from there, which was to include the line:
lons, lats = m.shiftdata(long, lat)
and then use the shifted data. That didn't have any visible impact.
My full sample code which generates both of the plots in Basemap and Bokeh is here:
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import pandas as pd
from bokeh.plotting import figure, show
from bokeh.sampledata.us_states import data as states
from bokeh.models import ColumnDataSource, Range1d
# read in data to use for plotted points
buildingdf = pd.read_csv('buildingdata.csv')
lat = buildingdf['latitude'].values
long = buildingdf['longitude'].values
# determine range to print based on min, max lat and long of the data
margin = .2 # buffer to add to the range
lat_min = min(lat) - margin
lat_max = max(lat) + margin
long_min = min(long) - margin
long_max = max(long) + margin
# create map using BASEMAP
m = Basemap(llcrnrlon=long_min,
llcrnrlat=lat_min,
urcrnrlon=long_max,
urcrnrlat=lat_max,
lat_0=(lat_max - lat_min)/2,
lon_0=(long_max-long_min)/2,
projection='merc',
resolution = 'h',
area_thresh=10000.,
)
m.drawcoastlines()
m.drawcountries()
m.drawstates()
m.drawmapboundary(fill_color='#46bcec')
m.fillcontinents(color = 'white',lake_color='#46bcec')
# convert lat and long to map projection coordinates
lons, lats = m(long, lat)
# plot points as red dots
m.scatter(lons, lats, marker = 'o', color='r')
plt.show()
# create map using Bokeh
source = ColumnDataSource(data = dict(lat = lat,lon = long))
# get state boundaries
state_lats = [states[code]["lats"] for code in states]
state_longs = [states[code]["lons"] for code in states]
p = figure(
toolbar_location="left",
plot_width=1100,
plot_height=700,
)
# limit the view to the min and max of the building data
p.y_range = Range1d(lat_min, lat_max)
p.x_range = Range1d(long_min, long_max)
p.xaxis.visible = False
p.yaxis.visible = False
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.patches(state_longs, state_lats, fill_alpha=0.0,
line_color="black", line_width=2, line_alpha=0.3)
p.circle(x="lon", y="lat", source = source, size=4.5,
fill_color='red',
line_color='grey',
line_alpha=.25
)
show(p)
I don't have enough reputation points to post a link to the data or to include it here.
In the basemap plot the scatter points are hidden behind the fillcontinents. Removing the two lines
#m.drawmapboundary(fill_color='#46bcec')
#m.fillcontinents(color = 'white',lake_color='#46bcec')
would show you the points. Because this might be undesired, the best solution would be to place the scatter on top of the rest of the map by using the zorder argument.
m.scatter(lons, lats, marker = 'o', color='r', zorder=5)
Here is the complete code (and I would like to ask you to include this kind of runnable minimal example with hardcoded data next time asking a question, as it saves everyone a lot of work inventing the data oneself):
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
import pandas as pd
import io
u = u"""latitude,longitude
42.357778,-71.059444
39.952222,-75.163889
25.787778,-80.224167
30.267222, -97.763889"""
# read in data to use for plotted points
buildingdf = pd.read_csv(io.StringIO(u), delimiter=",")
lat = buildingdf['latitude'].values
lon = buildingdf['longitude'].values
# determine range to print based on min, max lat and lon of the data
margin = 2 # buffer to add to the range
lat_min = min(lat) - margin
lat_max = max(lat) + margin
lon_min = min(lon) - margin
lon_max = max(lon) + margin
# create map using BASEMAP
m = Basemap(llcrnrlon=lon_min,
llcrnrlat=lat_min,
urcrnrlon=lon_max,
urcrnrlat=lat_max,
lat_0=(lat_max - lat_min)/2,
lon_0=(lon_max-lon_min)/2,
projection='merc',
resolution = 'h',
area_thresh=10000.,
)
m.drawcoastlines()
m.drawcountries()
m.drawstates()
m.drawmapboundary(fill_color='#46bcec')
m.fillcontinents(color = 'white',lake_color='#46bcec')
# convert lat and lon to map projection coordinates
lons, lats = m(lon, lat)
# plot points as red dots
m.scatter(lons, lats, marker = 'o', color='r', zorder=5)
plt.show()

matplotlib contour plot geojson output?

I'm using python matplotlib to generate contour plots from an 2D array of temperature data (stored in a NetCDF file), and I am interested in exporting the contour polygons and/or lines into geojson format so that I can use them outside of matplotlib. I have figured out that the "pyplot.contourf" function returns a "QuadContourSet" object which has a "collections" attribute that contains the coordinates of the contours:
contourSet = plt.contourf(data, levels)
collections = contourSet.collections
Does anyone know if matplotlib has a way to export the coordinates in "collections" to various formats, in particular geojson? I've searched the matplotlib documentation, and the web, and haven't come up with anything obvious.
Thanks!
geojsoncontour is a Python module that converts matplotlib contour lines to geojson.
It uses the following, simplified but complete, method to convert a matplotlib contour to geojson:
import numpy
from matplotlib.colors import rgb2hex
import matplotlib.pyplot as plt
from geojson import Feature, LineString, FeatureCollection
grid_size = 1.0
latrange = numpy.arange(-90.0, 90.0, grid_size)
lonrange = numpy.arange(-180.0, 180.0, grid_size)
X, Y = numpy.meshgrid(lonrange, latrange)
Z = numpy.sqrt(X * X + Y * Y)
figure = plt.figure()
ax = figure.add_subplot(111)
contour = ax.contour(lonrange, latrange, Z, levels=numpy.linspace(start=0, stop=100, num=10), cmap=plt.cm.jet)
line_features = []
for collection in contour.collections:
paths = collection.get_paths()
color = collection.get_edgecolor()
for path in paths:
v = path.vertices
coordinates = []
for i in range(len(v)):
lat = v[i][0]
lon = v[i][1]
coordinates.append((lat, lon))
line = LineString(coordinates)
properties = {
"stroke-width": 3,
"stroke": rgb2hex(color[0]),
}
line_features.append(Feature(geometry=line, properties=properties))
feature_collection = FeatureCollection(line_features)
geojson_dump = geojson.dumps(feature_collection, sort_keys=True)
with open('out.geojson', 'w') as fileout:
fileout.write(geojson_dump)
A good start to be sure to export all contours is to use the get_paths method when you iterate over the Collection objects and then the to_polygons method of Path to get numpy arrays:
http://matplotlib.org/api/path_api.html?highlight=to_polygons#matplotlib.path.Path.to_polygons.
Nevertheless the final formatting is up to you.
import matplotlib.pyplot as plt
cs = plt.contourf(data, levels)
for collection in cs.collections:
for path in collection.get_paths():
for polygon in path.to_polygons():
print polygon.__class__
print polygon