gtfs_stops_clustering 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/gtfs_stops_clustering/data_import.rb +20 -12
- data/lib/gtfs_stops_clustering/dbscan.rb +51 -40
- data/lib/gtfs_stops_clustering/input_consistency_checks.rb +52 -0
- data/lib/gtfs_stops_clustering/redis_geodata.rb +11 -9
- data/lib/gtfs_stops_clustering/version.rb +1 -1
- data/lib/gtfs_stops_clustering.rb +40 -38
- metadata +58 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d10d21f2d65afcf335560f35f8a34e4c8e46b3e6eeb6b7c74d9edf73e3336227
|
4
|
+
data.tar.gz: 67f4f260b43a6ac2f8af21b8c1a5471ee241b3c86f6a8532c55e0554268cdaa0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5ef45bb1402420422984e19918edfe60c5cc73fac8d503be3f605bd83c4b97460a688e79bba16e901cb42ecf358571cc1380467920e2c9b051be08b0fcff5fb
|
7
|
+
data.tar.gz: '0349797d0744abe6432ab20f262ac766118b2a3153b1e5dbb2796d2c3eb359fccc109bdadc17affbe43b9824457701daa5d3a1c434fe43ab40c3c1534dbce382'
|
@@ -1,12 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# lib/data_import.rb
|
2
4
|
|
3
|
-
require
|
4
|
-
require
|
5
|
+
require "csv"
|
6
|
+
require "gtfs"
|
5
7
|
|
8
|
+
# DataImport module
|
6
9
|
module DataImport
|
7
|
-
VERSION='0.0.1'
|
8
10
|
attr_accessor :data_import
|
9
11
|
|
12
|
+
# DataImport class
|
10
13
|
class DataImport
|
11
14
|
attr_accessor :stops, :stops_config_file, :stops_names, :stops_corner_cases, :stops_data, :stops_redis_geodata
|
12
15
|
|
@@ -22,13 +25,13 @@ module DataImport
|
|
22
25
|
end
|
23
26
|
|
24
27
|
def import_stops_corner_cases
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
return unless File.exist?(@stops_config_file)
|
29
|
+
|
30
|
+
CSV.foreach(@stops_config_file, headers: true) do |row|
|
31
|
+
stop_name = row["stop_name"]
|
32
|
+
cluster_name = row["cluster_name"]
|
29
33
|
|
30
|
-
|
31
|
-
end
|
34
|
+
stops_corner_cases << { stop_name: stop_name, cluster_name: cluster_name }
|
32
35
|
end
|
33
36
|
end
|
34
37
|
|
@@ -38,13 +41,20 @@ module DataImport
|
|
38
41
|
longitude = row.lon
|
39
42
|
stop_name = row.name
|
40
43
|
|
41
|
-
stop_name =
|
44
|
+
stop_name = stop_name_from_corner_cases(stop_name)
|
42
45
|
|
43
46
|
@stops_names << stop_name
|
44
47
|
@stops_data << [latitude, longitude]
|
45
48
|
@stops_redis_geodata << [longitude, latitude, "#{longitude},#{latitude}"]
|
46
49
|
end
|
47
50
|
end
|
51
|
+
|
52
|
+
def stop_name_from_corner_cases(stop_name)
|
53
|
+
csv_entry = @stops_corner_cases.find do |entry|
|
54
|
+
entry[:stop_name] == stop_name
|
55
|
+
end
|
56
|
+
csv_entry.nil? ? stop_name : csv_entry[:cluster_name]
|
57
|
+
end
|
48
58
|
end
|
49
59
|
|
50
60
|
def import_stops_data(*args)
|
@@ -56,5 +66,3 @@ module DataImport
|
|
56
66
|
}
|
57
67
|
end
|
58
68
|
end
|
59
|
-
|
60
|
-
include DataImport
|
@@ -1,33 +1,47 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require 'text'
|
5
|
-
require 'geocoder'
|
6
|
-
require_relative 'redis_geodata'
|
3
|
+
# lib/gtfs_stops_clustering/dbscan.rb
|
7
4
|
|
5
|
+
require "distance_measures"
|
6
|
+
require "text"
|
7
|
+
require "geocoder"
|
8
|
+
require_relative "redis_geodata"
|
9
|
+
|
10
|
+
# Array class
|
8
11
|
class Array
|
9
|
-
def haversine_distance2(
|
10
|
-
Geocoder::Calculations.distance_between(self,
|
12
|
+
def haversine_distance2(other)
|
13
|
+
Geocoder::Calculations.distance_between(self, other)
|
11
14
|
end
|
12
15
|
end
|
13
16
|
|
17
|
+
# DBSCAN module
|
14
18
|
module DBSCAN
|
19
|
+
# Clusterer class
|
15
20
|
class Clusterer
|
21
|
+
include RedisGeodata
|
16
22
|
attr_accessor :points, :options, :clusters
|
17
23
|
|
18
24
|
def initialize(points, stops_redis_geodata, options = {})
|
19
25
|
options[:distance] = :euclidean_distance unless options[:distance]
|
20
26
|
options[:labels] = [] unless options[:labels]
|
21
27
|
|
22
|
-
c = 0
|
23
28
|
redis_geodata_import(stops_redis_geodata, options[:epsilon])
|
24
|
-
@points = points.map { |e| po = Point.new(e, options[:labels][c]); c +=1; po }
|
25
29
|
@options = options
|
26
|
-
|
30
|
+
init_points(points)
|
31
|
+
@clusters = { -1 => [] }
|
27
32
|
|
28
33
|
clusterize!
|
29
34
|
end
|
30
35
|
|
36
|
+
def init_points(points)
|
37
|
+
c = 0
|
38
|
+
@points = points.map do |e|
|
39
|
+
po = Point.new(e, @options[:labels][c])
|
40
|
+
c += 1
|
41
|
+
po
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
31
45
|
def clusterize!
|
32
46
|
current_cluster = -1
|
33
47
|
@points.each do |point|
|
@@ -49,10 +63,10 @@ module DBSCAN
|
|
49
63
|
# Get Cluster Position
|
50
64
|
cluster_pos = find_cluster_position(clusters[current_cluster])
|
51
65
|
|
52
|
-
clusters[current_cluster].each
|
66
|
+
clusters[current_cluster].each do |e|
|
53
67
|
e.cluster_name = cluster_name
|
54
68
|
e.cluster_pos = cluster_pos
|
55
|
-
|
69
|
+
end
|
56
70
|
else
|
57
71
|
clusters[-1].push(point)
|
58
72
|
end
|
@@ -91,9 +105,11 @@ module DBSCAN
|
|
91
105
|
neighbors = []
|
92
106
|
geosearch_results = geosearch(point.items[1], point.items[0])
|
93
107
|
geosearch_results.each do |neighbor_pos|
|
94
|
-
coordinates = neighbor_pos.split(
|
95
|
-
neighbor = @points.find
|
96
|
-
|
108
|
+
coordinates = neighbor_pos.split(",")
|
109
|
+
neighbor = @points.find do |elem|
|
110
|
+
elem.items[0] == coordinates[1] &&
|
111
|
+
elem.items[1] == coordinates[0]
|
112
|
+
end
|
97
113
|
next unless neighbor
|
98
114
|
|
99
115
|
string_distance = Text::Levenshtein.distance(point.label.downcase, neighbor.label.downcase)
|
@@ -112,9 +128,7 @@ module DBSCAN
|
|
112
128
|
|
113
129
|
if new_points.size >= options[:min_points]
|
114
130
|
new_points.each do |p|
|
115
|
-
unless neighbors.include?(p)
|
116
|
-
neighbors.push(p)
|
117
|
-
end
|
131
|
+
neighbors.push(p) unless neighbors.include?(p)
|
118
132
|
end
|
119
133
|
end
|
120
134
|
end
|
@@ -127,32 +141,31 @@ module DBSCAN
|
|
127
141
|
|
128
142
|
cluster_points
|
129
143
|
end
|
130
|
-
end
|
131
144
|
|
132
|
-
|
133
|
-
|
134
|
-
|
145
|
+
def find_cluster_name(labels)
|
146
|
+
words = labels.map { |label| label.strip.split }
|
147
|
+
common_title = ""
|
135
148
|
|
136
|
-
|
137
|
-
|
138
|
-
|
149
|
+
# Loop through each word index starting from the first
|
150
|
+
(0...words.first.length).each do |i|
|
151
|
+
words_at_index = words.map { |word_list| word_list[i] }
|
139
152
|
|
140
|
-
|
153
|
+
break unless words_at_index.uniq.length == 1
|
141
154
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
common_title.strip! ? common_title : labels.first
|
146
|
-
end
|
155
|
+
common_title += " #{words_at_index.first.capitalize}"
|
156
|
+
end
|
147
157
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
158
|
+
common_title.strip ? common_title : labels.first
|
159
|
+
end
|
160
|
+
def find_cluster_position(cluster)
|
161
|
+
total_lat = cluster.map { |e| e.items[0].to_f }.sum
|
162
|
+
total_lon = cluster.map { |e| e.items[1].to_f }.sum
|
163
|
+
avg_lat = total_lat / cluster.size
|
164
|
+
avg_lon = total_lon / cluster.size
|
165
|
+
[avg_lat, avg_lon]
|
166
|
+
end
|
154
167
|
end
|
155
|
-
|
168
|
+
# Point class
|
156
169
|
class Point
|
157
170
|
attr_accessor :items, :cluster, :visited, :label, :cluster_name, :cluster_pos
|
158
171
|
|
@@ -176,5 +189,3 @@ module DBSCAN
|
|
176
189
|
clusterer.labeled_results
|
177
190
|
end
|
178
191
|
end
|
179
|
-
|
180
|
-
include DBSCAN
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# lib/input_consistency_checks.rb
|
4
|
+
|
5
|
+
# InputConsistencyChecks module
|
6
|
+
module InputConsistencyChecks
|
7
|
+
# InputConsistencyChecks class
|
8
|
+
class InputConsistencyChecks
|
9
|
+
attr_accessor :gtfs_paths, :epsilon, :min_points, :names_similarity, :stops_config_path
|
10
|
+
|
11
|
+
def initialize(gtfs_paths, epsilon, min_points, names_similarity, stops_config_path)
|
12
|
+
@gtfs_paths = gtfs_paths
|
13
|
+
@stops_config_path = stops_config_path
|
14
|
+
@epsilon = epsilon
|
15
|
+
@min_points = min_points
|
16
|
+
@names_similarity = names_similarity
|
17
|
+
input_consistency_checks
|
18
|
+
end
|
19
|
+
|
20
|
+
def input_consistency_checks
|
21
|
+
gtfs_paths_check
|
22
|
+
epsilon_check
|
23
|
+
min_points_check
|
24
|
+
names_similarity_check
|
25
|
+
end
|
26
|
+
|
27
|
+
def gtfs_paths_check
|
28
|
+
raise ArgumentError, "gtfs_paths cannot be nil" if @gtfs_paths.nil?
|
29
|
+
raise ArgumentError, "gtfs_paths must be an Array" unless @gtfs_paths.is_a?(Array)
|
30
|
+
raise ArgumentError, "gtfs_paths must not be empty" if @gtfs_paths.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def epsilon_check
|
34
|
+
raise ArgumentError, "epsilon must be a Float" unless @epsilon.is_a?(Float)
|
35
|
+
raise ArgumentError, "epsilon must be greater than 0" if @epsilon.negative?
|
36
|
+
end
|
37
|
+
|
38
|
+
def min_points_check
|
39
|
+
raise ArgumentError, "min_points must be an Integer" unless @min_points.is_a?(Integer)
|
40
|
+
raise ArgumentError, "min_points must be greater than 0" if @min_points.negative?
|
41
|
+
end
|
42
|
+
|
43
|
+
def names_similarity_check
|
44
|
+
raise ArgumentError, "names_similarity must be a Float" unless @names_similarity.is_a?(Float)
|
45
|
+
raise ArgumentError, "names_similarity must be between 0 and 1" if @names_similarity.negative? || @names_similarity > 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def input_consistency_checks(gtfs_paths, epsilon, min_points, names_similarity, stop_config_path)
|
50
|
+
@input_consistency_checks = InputConsistencyChecks.new(gtfs_paths, epsilon, min_points, names_similarity, stop_config_path)
|
51
|
+
end
|
52
|
+
end
|
@@ -1,17 +1,21 @@
|
|
1
|
-
#
|
2
|
-
require 'redis'
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
3
|
+
# lib/gtfs_stops_clustering/redis_geodata.rb
|
4
|
+
|
5
|
+
require "redis"
|
6
|
+
|
7
|
+
# RedisGeodata module
|
4
8
|
module RedisGeodata
|
5
|
-
VERSION='0.0.1'
|
6
9
|
attr_accessor :redis
|
7
10
|
|
11
|
+
# RedisGeodata class
|
8
12
|
class RedisGeodata
|
9
13
|
attr_accessor :stops, :key, :redis, :epsilon
|
10
14
|
|
11
15
|
def initialize(stops, epsilon)
|
12
|
-
@redis = Redis.new(url:
|
16
|
+
@redis = Redis.new(url: "redis://127.0.0.1:6379")
|
13
17
|
@stops = stops
|
14
|
-
@key =
|
18
|
+
@key = "stops"
|
15
19
|
@epsilon = epsilon
|
16
20
|
geoadd
|
17
21
|
end
|
@@ -22,8 +26,8 @@ module RedisGeodata
|
|
22
26
|
end
|
23
27
|
|
24
28
|
def geosearch(longitude, latitude)
|
25
|
-
list = @redis.georadius(@key, longitude, latitude, @epsilon,
|
26
|
-
list.reject! { |point| point == longitude
|
29
|
+
list = @redis.georadius(@key, longitude, latitude, @epsilon, "km")
|
30
|
+
list.reject! { |point| point == "#{longitude},#{latitude}" }
|
27
31
|
list
|
28
32
|
end
|
29
33
|
end
|
@@ -36,5 +40,3 @@ module RedisGeodata
|
|
36
40
|
@redis.geosearch(*args)
|
37
41
|
end
|
38
42
|
end
|
39
|
-
|
40
|
-
include RedisGeodata
|
@@ -1,31 +1,37 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
# lib/gtfs_stops_clustering.rb
|
3
|
-
|
4
|
-
require
|
4
|
+
|
5
|
+
require "rubygems"
|
6
|
+
require "bundler/setup"
|
5
7
|
require_relative "gtfs_stops_clustering/version"
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require_relative
|
9
|
-
require_relative
|
8
|
+
require "gtfs"
|
9
|
+
require "csv"
|
10
|
+
require_relative "./gtfs_stops_clustering/data_import"
|
11
|
+
require_relative "./gtfs_stops_clustering/dbscan"
|
12
|
+
require_relative "./gtfs_stops_clustering/input_consistency_checks"
|
10
13
|
|
14
|
+
# GtfsStopClustering module
|
11
15
|
module GtfsStopsClustering
|
12
|
-
VERSION = GtfsStopsClustering::VERSION
|
13
16
|
attr_accessor :gtfs_stops_clustering
|
14
17
|
|
18
|
+
# GtfsStopsClustering class
|
15
19
|
class GtfsStopsClustering
|
16
|
-
|
20
|
+
include InputConsistencyChecks
|
21
|
+
include DataImport
|
22
|
+
include DBSCAN
|
23
|
+
attr_accessor :clusters, :gtfs_paths, :gtfs_stops, :stops_config_path, :epsilon, :min_points, :names_similarity
|
17
24
|
|
18
|
-
def initialize(
|
25
|
+
def initialize(gtfs_paths, epsilon, min_points, names_similarity, stops_config_path)
|
26
|
+
@gtfs_paths = gtfs_paths
|
27
|
+
@stops_config_path = stops_config_path
|
28
|
+
@epsilon = epsilon
|
29
|
+
@min_points = min_points
|
30
|
+
@names_similarity = names_similarity
|
31
|
+
input_consistency_checks(@gtfs_paths, @epsilon, @min_points, @names_similarity, @stops_config_path)
|
19
32
|
@clusters = []
|
20
|
-
|
21
|
-
|
22
|
-
@stops_config_path = stops_config_path
|
23
|
-
@epsilon = epsilon
|
24
|
-
@min_points = min_points
|
25
|
-
@names_similarity = names_similarity
|
26
|
-
@gtfs_stops = create_stops_merged
|
27
|
-
clusterize_stops_csv(@gtfs_stops)
|
28
|
-
end
|
33
|
+
@gtfs_stops = create_stops_merged
|
34
|
+
clusterize_stops
|
29
35
|
end
|
30
36
|
|
31
37
|
def create_stops_merged
|
@@ -37,11 +43,20 @@ module GtfsStopsClustering
|
|
37
43
|
gtfs_stops.flatten
|
38
44
|
end
|
39
45
|
|
40
|
-
def
|
41
|
-
data = import_stops_data(
|
42
|
-
@clusters = DBSCAN(
|
46
|
+
def clusterize_stops
|
47
|
+
data = import_stops_data(@gtfs_stops, @stops_config_path)
|
48
|
+
@clusters = DBSCAN(data[:stops_data],
|
49
|
+
data[:stops_redis_geodata],
|
50
|
+
epsilon: @epsilon,
|
51
|
+
min_points: @min_points,
|
52
|
+
similarity: @names_similarity,
|
53
|
+
distance: :haversine_distance2,
|
54
|
+
labels: data[:stops_names])
|
55
|
+
map_clustered_stops
|
56
|
+
end
|
43
57
|
|
44
|
-
|
58
|
+
def map_clustered_stops
|
59
|
+
@clusters.each_value do |cluster|
|
45
60
|
cluster.each do |stop|
|
46
61
|
gtfs_stop = @gtfs_stops.find { |e| e.lat == stop[:stop_lat] && e.lon == stop[:stop_lon] }
|
47
62
|
stop[:stop_id] = gtfs_stop.id
|
@@ -49,24 +64,11 @@ module GtfsStopsClustering
|
|
49
64
|
stop[:parent_station] = gtfs_stop.parent_station
|
50
65
|
end
|
51
66
|
end
|
52
|
-
|
53
|
-
output_path = 'stop_clusters.txt'
|
54
|
-
File.open(output_path, 'w') do |file|
|
55
|
-
@clusters.each do |cluster_id, cluster |
|
56
|
-
file.puts "Cluster #{cluster_id}"
|
57
|
-
cluster.each do |point|
|
58
|
-
file.puts point.inspect
|
59
|
-
end
|
60
|
-
file.puts
|
61
|
-
end
|
62
|
-
end
|
63
67
|
end
|
64
68
|
end
|
65
69
|
|
66
|
-
def
|
67
|
-
@gtfs_stops_clustering = GtfsStopsClustering.new(
|
70
|
+
def build_clusters(gtfs_paths, epsilon, min_points, names_similarity = 1, stop_config_path = "")
|
71
|
+
@gtfs_stops_clustering = GtfsStopsClustering.new(gtfs_paths, epsilon, min_points, names_similarity, stop_config_path)
|
68
72
|
@gtfs_stops_clustering.clusters
|
69
73
|
end
|
70
74
|
end
|
71
|
-
|
72
|
-
include GtfsStopsClustering
|
metadata
CHANGED
@@ -1,29 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gtfs_stops_clustering
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Pietro Visconti
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
11
|
date: 2023-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: csv
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: '3.2'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.2.8
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - "~>"
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
29
|
+
version: '3.2'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.2.8
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: distance_measures
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,85 +45,107 @@ dependencies:
|
|
39
45
|
- !ruby/object:Gem::Version
|
40
46
|
version: 0.0.6
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
48
|
+
name: geocoder
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
44
50
|
requirements:
|
45
51
|
- - "~>"
|
46
52
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
53
|
+
version: '1.8'
|
48
54
|
- - ">="
|
49
55
|
- !ruby/object:Gem::Version
|
50
|
-
version: 1.
|
56
|
+
version: 1.8.2
|
51
57
|
type: :runtime
|
52
58
|
prerelease: false
|
53
59
|
version_requirements: !ruby/object:Gem::Requirement
|
54
60
|
requirements:
|
55
61
|
- - "~>"
|
56
62
|
- !ruby/object:Gem::Version
|
57
|
-
version: '1.
|
63
|
+
version: '1.8'
|
58
64
|
- - ">="
|
59
65
|
- !ruby/object:Gem::Version
|
60
|
-
version: 1.
|
66
|
+
version: 1.8.2
|
61
67
|
- !ruby/object:Gem::Dependency
|
62
|
-
name:
|
68
|
+
name: gtfs
|
63
69
|
requirement: !ruby/object:Gem::Requirement
|
64
70
|
requirements:
|
65
71
|
- - "~>"
|
66
72
|
- !ruby/object:Gem::Version
|
67
|
-
version:
|
68
|
-
- - ">="
|
69
|
-
- !ruby/object:Gem::Version
|
70
|
-
version: 1.8.2
|
73
|
+
version: 0.4.1
|
71
74
|
type: :runtime
|
72
75
|
prerelease: false
|
73
76
|
version_requirements: !ruby/object:Gem::Requirement
|
74
77
|
requirements:
|
75
78
|
- - "~>"
|
76
79
|
- !ruby/object:Gem::Version
|
77
|
-
version:
|
78
|
-
- - ">="
|
79
|
-
- !ruby/object:Gem::Version
|
80
|
-
version: 1.8.2
|
80
|
+
version: 0.4.1
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
|
-
name:
|
82
|
+
name: redis
|
83
83
|
requirement: !ruby/object:Gem::Requirement
|
84
84
|
requirements:
|
85
85
|
- - "~>"
|
86
86
|
- !ruby/object:Gem::Version
|
87
|
-
version: '
|
87
|
+
version: '5.0'
|
88
88
|
- - ">="
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version:
|
90
|
+
version: 5.0.8
|
91
91
|
type: :runtime
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
95
|
- - "~>"
|
96
96
|
- !ruby/object:Gem::Version
|
97
|
-
version: '
|
97
|
+
version: '5.0'
|
98
98
|
- - ">="
|
99
99
|
- !ruby/object:Gem::Version
|
100
|
-
version:
|
100
|
+
version: 5.0.8
|
101
101
|
- !ruby/object:Gem::Dependency
|
102
|
-
name:
|
102
|
+
name: text
|
103
103
|
requirement: !ruby/object:Gem::Requirement
|
104
104
|
requirements:
|
105
105
|
- - "~>"
|
106
106
|
- !ruby/object:Gem::Version
|
107
|
-
version: '
|
107
|
+
version: '1.3'
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
110
|
+
version: 1.3.1
|
111
111
|
type: :runtime
|
112
112
|
prerelease: false
|
113
113
|
version_requirements: !ruby/object:Gem::Requirement
|
114
114
|
requirements:
|
115
115
|
- - "~>"
|
116
116
|
- !ruby/object:Gem::Version
|
117
|
-
version: '
|
117
|
+
version: '1.3'
|
118
118
|
- - ">="
|
119
119
|
- !ruby/object:Gem::Version
|
120
|
-
version:
|
120
|
+
version: 1.3.1
|
121
|
+
- !ruby/object:Gem::Dependency
|
122
|
+
name: minitest
|
123
|
+
requirement: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - "~>"
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '5.20'
|
128
|
+
type: :development
|
129
|
+
prerelease: false
|
130
|
+
version_requirements: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - "~>"
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '5.20'
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: pry
|
137
|
+
requirement: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - "~>"
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 0.14.2
|
142
|
+
type: :development
|
143
|
+
prerelease: false
|
144
|
+
version_requirements: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - "~>"
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: 0.14.2
|
121
149
|
description: A gem to read GTFS stops data and create clusters based on coordinates
|
122
150
|
and stop names' similarities.
|
123
151
|
email:
|
@@ -129,6 +157,7 @@ files:
|
|
129
157
|
- lib/gtfs_stops_clustering.rb
|
130
158
|
- lib/gtfs_stops_clustering/data_import.rb
|
131
159
|
- lib/gtfs_stops_clustering/dbscan.rb
|
160
|
+
- lib/gtfs_stops_clustering/input_consistency_checks.rb
|
132
161
|
- lib/gtfs_stops_clustering/redis_geodata.rb
|
133
162
|
- lib/gtfs_stops_clustering/version.rb
|
134
163
|
homepage: https://github.com/Visco01/gtfs_stops_clustering
|