Skip to content

Instantly share code, notes, and snippets.

@straypacket
Last active December 18, 2015 12:09
Show Gist options
  • Save straypacket/5780848 to your computer and use it in GitHub Desktop.
Save straypacket/5780848 to your computer and use it in GitHub Desktop.
Walk server - from a road .shp file to a full walk
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<title>SUJ Geo walks</title>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" ></script>
<script type="text/javascript">
var path = [];
function getNewPath() {
// Make ajax call
var xhReq = new XMLHttpRequest();
xhReq.open("GET", "/?lon="+$('.lon').val()+"&lat="+$('.lat').val()+"&length="+$('.length').val(), false);
xhReq.send(null);
var serverResponse = xhReq.responseText;
var objJSON = JSON.parse(serverResponse);
// Update data
path = [];
objJSON['body'].forEach( function(p) {
if (p) {
path.push(new google.maps.LatLng(p[1],p[0]));
}
else{
console.log(objJSON['body'])
}
});
// Dumb refresh
initialize();
}
function initialize() {
var mapDiv = document.getElementById('map-canvas');
var map = new google.maps.Map(mapDiv, {
center: new google.maps.LatLng($('.lat').val(),$('.lon').val()),
zoom: 15,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var lineSymbol = {
path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW
};
var line = new google.maps.Polyline({
path: path,
icons: [{
icon: lineSymbol,
offset: '100%'
}],
strokeColor: '#ff0000',
strokeOpacity: 1.0,
strokeWeight: 2
});
google.maps.LatLng.prototype.kmTo = function(a){
var e = Math, ra = e.PI/180;
var b = this.lat() * ra, c = a.lat() * ra, d = b - c;
var g = this.lng() * ra - a.lng() * ra;
var f = 2 * e.asin(e.sqrt(e.pow(e.sin(d/2), 2) + e.cos(b) * e.cos(c) * e.pow(e.sin(g/2), 2)));
return f * 6378.137;
}
var inKm = function(n){
var a = n, len = a.length, dist = 0;
for(var i=0; i<len-1; i++){
dist += a[i].kmTo(a[i+1]);
}
return dist;
}
$('.info').html("Route length: "+inKm(path).toPrecision(4)+" Km");
line.setMap(map);
function animateArrow() {
var count = 0;
offsetId = window.setInterval(function() {
count = (count + 1) % 200;
var icons = line.get('icons');
icons[0].offset = (count / 2) + '%';
line.set('icons', icons);
}, 50);
}
animateArrow();
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body style="font-family: Arial; border: 0 none;">
<div id="map-canvas" style="width: 800px; height: 400px"></div>
<div>
Lon: <input class="lon" type="text" name="lon" value="139.698345">
Lat: <input class="lat" type="text" name="lat" value="35.666641">
Length: <input class="length" type="text" name="length" value="2000">m<br>
<input type="submit" onclick="getNewPath()" value="Get new route">
</div>
<div class="info"></div>
</body>
</html>
####
#Old spherical indexes
###
db.road_coords.ensureIndex({"head": "2d"})
db.road_coords.ensureIndex({"tail": "2d"})
db.station_coords.ensureIndex({"idx_loc": "2d"})
db.road_coords.find( { head: { $near: [138.657956,35.858569], $maxDistance: 1 } } )
###
# Near sherical indexes
###
db.road_coords.ensureIndex({"head": "2dsphere"})
db.road_coords.ensureIndex({"tail": "2dsphere"})
db.station_coords.ensureIndex({"idx_loc": "2dsphere"})
# Find closest locations within 20Km
db.road_coords.find( { head: { $nearSphere: [138.657956,35.858569], $maxDistance: 18 / 6371 } } )
#{
# "_id":ObjectId("51baf5608876e476ac000001"),
# "head":{
# "lat":138.857121,
# "lon":35.86131
# },
# "tail":{
# "lat":138.857563,
# "lon":35.861237
# },
# "body":[
# [
# 138.857121,
# 35.86131
# ],
# [
# 138.857259,
# 35.861265
# ],
# [
# 138.857563,
# 35.861237
# ]
# ]
#}
# Find locations within 20Km
db.road_coords.find( { head: { $geoWithin: { $centerSphere: [ [138.657956,35.858569], 20 / 6371 ] } } } )
#{
# "_id":ObjectId("51baf5608876e476ac000001"),
# "head":{
# "lat":138.857121,
# "lon":35.86131
# },
# "tail":{
# "lat":138.857563,
# "lon":35.861237
# },
# "body":[
# [
# 138.857121,
# 35.86131
# ],
# [
# 138.857259,
# 35.861265
# ],
# [
# 138.857563,
# 35.861237
# ]
# ]
#}
# Order by and show distance
# This will work with only one index per collection :(
# distanceMultiplier: 6371 will make mongo calculate the distances in Km (ue 3959 or miles)
db.road_coords_head.ensureIndex({"tail": "2dsphere"})
db.road_coords_tail.ensureIndex({"tail": "2dsphere"})
db.station_coords.ensureIndex({"idx_loc": "2dsphere"})
db.runCommand( { geoNear: "road_coords_head", near: [138.657956,35.858569], spherical: true, distanceMultiplier: 6371, limit: 5 } )
#{
# "ns" : "roadsimulator.road_coords",
# "results" : [
# {
# "dis" : 17.950952246422005,
# "obj" : {
# "_id" : ObjectId("51baf5608876e476ac000001"),
# "head" : {
# "lat" : 138.857121,
# "lon" : 35.86131
# },
# "tail" : {
# "lat" : 138.857563,
# "lon" : 35.861237
# },
# "body" : [
# [
# 138.857121,
# 35.86131
# ],
# [
# 138.857259,
# 35.861265
# ],
# [
# 138.857563,
# 35.861237
# ]
# ]
# }
# },
#...
require 'net/http'
url = 'http://localhost:4570/?long=139.6941&lat=35.6572&length=2000'
resp = Net::HTTP.get_response(URI.parse(url))
data = resp.body
##
# Call with curl -i -H "Accept: application/json" "http://localhost:4570/?long=139.6941&lat=35.6572&length=2000"
# Or use http://localhost:4570/demo.html
##
require 'rubygems'
require 'active_support'
require 'sinatra'
require 'mongo'
require 'json'
encoding_options = {
:invalid => :replace, # Replace invalid byte sequences
:undef => :replace, # Replace anything not defined in ASCII
:replace => '', # Use a blank for those replacements
:universal_newline => true # Always break lines with \n
}
class Array
def rotate
push shift
end
end
conn = Mongo::Connection.new("localhost", 27017, :pool_size => 100, :pool_timeout => 5)
db = conn['roadsimulator']
set :port, 4570
get '/' do
# Get params
lon = params["lon"].to_f
lat = params["lat"].to_f
# Convert from Km to degrees
length = params["length"].to_f/111110
# Build walk
res = make_walk(db, lon, lat, length)
# Make a google compatible walk
#res['body'].each do |w|
# w.rotate
#end
# Output to paste into https://code.google.com/apis/ajax/playground/#polylines_v3
#res['body'].each do |w|
# puts "new google.maps.LatLng(#{w[1]}, #{w[0]}),"
#end
# Send walk
res.to_json
end
####
# Make big walk
####
def make_walk(db, lon, lat, length)
# Initial run
limit = 3
local_arch = get_arch(db, lon, lat, limit)
# Inits
walk = ActiveSupport::OrderedHash.new
walk['distance'] = local_arch['obj']['distance']
walk['body'] = [local_arch['obj']['body']]
walked = {}
rewalk_thresh = 1
# Loop until we reach the requested length
while walk['distance'] <= length
if local_arch['obj']['body']
old_body = local_arch['obj']['body']
local_arch = get_arch(db, old_body[old_body.length()-1][0], old_body[old_body.length()-1][1], limit)
# Is this an already discovered path
if walked.include? local_arch['obj']
walked[local_arch['obj']] = walked[local_arch['obj']]+1
# If we walk past this arch for more than 5 times, walk back until we find a fresh path
if walked[local_arch['obj']] >= rewalk_thresh
# Walk back/ Pop dubious path from walked array
walk['body'].pop()
# Update current position to last position after popping
local_arch['obj']['body'] = walk['body'][walk['body'].length()-1]
#Increase the search limit
limit += 1
# Get another arch
next
end
else # New path
walked[local_arch['obj']] = 1
end
else
# We popped too much! Starting from scratch ...
local_arch = get_arch(db, lon, lat, limit)
next
end
# Add body array to walk
walk['body'].push(local_arch['obj']['body']) if local_arch
# Accumulate distance
walk['distance'] += (local_arch['obj']['distance']) if local_arch
end
# Flatten array so that archs are merged into contiguous [lon,lat] pairs
walk['body'] = walk['body'].flatten(1)
return walk
end
####
# Add arch to walking path
####
def get_arch(db, lon, lat, limit)
# Build query
selector = ActiveSupport::OrderedHash.new
selector['geoNear'] = 'road_coords_head'
selector['near'] = [lon, lat]
selector['spherical'] = true
selector['distanceMultiplier'] = 6371
selector['limit'] = limit
# Query
res = db.command( selector )
# Get which tail_point is closer to a train station
selector['geoNear'] = 'station_coords'
ordered_res = []
# TO DO
# When reaching a train station, travel a few train
# stations on that line
#For each of the candidate arcs (closest arcs)
res['results'].each do |p|
# Make query
selector['near'] = p['obj']['body'][-1]
station_res = db.command( selector )
# Average the distance from the last point of the arc
# to the closest _limit_ train distances
# TO DO: play with this _limit_ variable
sdist = 0.0
station_res['results'].each { |sd| sdist += sd['dis'] }
avg_sdist = sdist/station_res['results'].size
# Push to array with original data and
# newly calculated distance to train stations
ordered_res.push({'dist' => avg_sdist, 'arc' => p})
end
# Sort new array
sorted = ordered_res.sort_by { |k| k["dist"] }
# Generate random element, based on a Gaussian distribution
r = (rand(limit) - (limit-2)).abs
return sorted[r]['arc']
end
require 'rgeo'
require 'rgeo-shapefile'
require 'mongo'
conn = Mongo::Connection.new("localhost", 27017, :pool_size => 100, :pool_timeout => 5)
db = conn['roadsimulator']
indexes = ['head','tail']
# Parse Shapefile
col = db['station_coords']
RGeo::Shapefile::Reader.open('data/N05-12_Station2.shp') do |file|
puts "File contains #{file.num_records} records."
file.each do |record|
#路線名
line = record.attributes['N05_002'] = record.attributes['N05_002'].force_encoding('sjis').encode('utf-8')
#運営会社
company = record.attributes['N05_003'] = record.attributes['N05_003'].force_encoding('sjis').encode('utf-8')
#駅名
station = record.attributes['N05_011'] = record.attributes['N05_011'].force_encoding('sjis').encode('utf-8')
#Point
index_coord = record.geometry
#Create query
p = {
:idx_loc => {
:lat => index_coord.x().to_f,
:lon => index_coord.y().to_f
},
:station => station,
:line => line,
:company => company
}
#Insert into MongoDB
col.insert(p)
end
end
return 0
exit
indexes.each do |i|
col = db['road_coords_'+i]
# Parse highway Shapefile
RGeo::Shapefile::Reader.open('data/tokyo_highway.shp') do |file|
puts "File contains #{file.num_records} records."
count = []
file.each do |record|
first = record.geometry[0].point_n(0)
last = record.geometry[0].point_n(record.geometry[0].num_points()-1)
total = record.geometry[0].num_points()
index_coord = first
if i == 'tail'
index_coord = last
end
#Create query
p = {
:idx_loc => {
:lat => index_coord.x().to_f,
:lon => index_coord.y().to_f
},
:type => i,
:body => []
}
distance = 0.0
pp = nil
record.geometry[0].points.each do |c|
# Measure the distance between inner points
if pp
distance += pp.distance(c)
end
p[:body].push([c.x(),c.y()])
pp = c
end
# Add arch's distance
p[:distance] = distance
#Insert into MongoDB
col.insert(p)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment