slippy_tiles_scorer 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +10 -0
- data/CHANGELOG.md +5 -0
- data/README.md +103 -0
- data/Rakefile +9 -0
- data/lib/slippy_tiles_scorer/cluster.rb +68 -0
- data/lib/slippy_tiles_scorer/max_square.rb +55 -0
- data/lib/slippy_tiles_scorer/version.rb +5 -0
- data/lib/slippy_tiles_scorer.rb +65 -0
- data/sig/slippy_tiles_scorer.rbs +4 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3535ebc0ef81b4b9096043ac739f65877a793c82e9f115f4faa16339184c359c
|
4
|
+
data.tar.gz: f0375a478ee6603038e12fc15f55ecf320c161d2764d30fbd362908bfaf763b9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0df4fbebb75b3f9b3a4019490695d6ae81e9d59498a1479f72f63f92de17ad756b1e1776787025e06cebeff2770aab067faff79aa6f6a2053757daf7972d5a34
|
7
|
+
data.tar.gz: 1c5b8bc92a68023c2fb8fbd84f9bba559eeb1b5eec26b757b7310c22a0aacbf316700736f4c814b31e2871a57b279f26d951d576d8861104c5b2460e10771a6e
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# SlippyTilesScorer
|
2
|
+
|
3
|
+
Calculate scores of map tiles (x, y) on a map. The scores are total, (max)
|
4
|
+
clusters, and max squares.
|
5
|
+
|
6
|
+
To experiment with that code, run `bin/console` for an interactive prompt.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
11
|
+
|
12
|
+
Install the gem and add to the application's Gemfile by executing:
|
13
|
+
|
14
|
+
```bash
|
15
|
+
bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
16
|
+
```
|
17
|
+
|
18
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
19
|
+
|
20
|
+
```bash
|
21
|
+
gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
22
|
+
```
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Find some examples below.
|
27
|
+
|
28
|
+
### Simple
|
29
|
+
|
30
|
+
A simple example of how to use the `TileScorer` class, when tinkering in a REPL.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'tile_scorer'
|
34
|
+
|
35
|
+
collection_of_tiles = Set.new
|
36
|
+
collection_of_tiles.add([0, 0])
|
37
|
+
collection_of_tiles.add([0, 1])
|
38
|
+
collection_of_tiles.add([1, 0])
|
39
|
+
collection_of_tiles.add([1, 1])
|
40
|
+
collection_of_tiles.add([0, 2])
|
41
|
+
collection_of_tiles.add([1, 2])
|
42
|
+
collection_of_tiles.add([2, 0])
|
43
|
+
collection_of_tiles.add([2, 1])
|
44
|
+
collection_of_tiles.add([2, 2])
|
45
|
+
|
46
|
+
tile_scorer = SlippyTilesScorer::Scorer.new(tiles_x_y: collection_of_tiles)
|
47
|
+
tile_scorer.valid? # => true
|
48
|
+
|
49
|
+
# the collection_of_tiles is a 3x3 map and will be used to calculate scores,
|
50
|
+
# unless you provide a different set of tiles, when calling the methods.
|
51
|
+
|
52
|
+
tile_scorer.visited # => 9
|
53
|
+
|
54
|
+
# calculate clusters
|
55
|
+
tile_scorer.clusters # =>
|
56
|
+
# {
|
57
|
+
# :clusters=>[
|
58
|
+
# <Set: {[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [1, 2], [2, 2], [2, 1], [2, 0]}>
|
59
|
+
# ],
|
60
|
+
# :cluster_tiles=>#<Set: {[1, 1]}>,
|
61
|
+
# :clusters_of_cluster_tiles=>[]
|
62
|
+
# }
|
63
|
+
|
64
|
+
# calculate max squares
|
65
|
+
tile_scorer.max_squares # => {:size=>3, top_left_tile_x_y=>#<Set: {[0, 0]}>}
|
66
|
+
tile_scorer.max_squares(min_size: 4) # => {:size=>4, top_left_tile_x_y=>#<Set: {}>}
|
67
|
+
```
|
68
|
+
|
69
|
+
### Optimized
|
70
|
+
|
71
|
+
An more computationally optimized example of how to use the `TileScorer` class, when using the code in production.
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
### OPTIMIZE COMPUTATION ###
|
75
|
+
tile_scorer = SlippyTilesScorer::Scorer.new(tiles_x_y: collection_of_tiles)
|
76
|
+
# Optimize computation by calculating clusters first,
|
77
|
+
result_clusters = tile_scorer.clusters
|
78
|
+
clusters = result_clusters[:clusters]
|
79
|
+
# then max squares based on the clusters.
|
80
|
+
max_squares_of_clusters = clusters.flat_map do |cluster|
|
81
|
+
tile_scorer.max_squares(tiles_x_y: cluster)
|
82
|
+
end
|
83
|
+
# Find the max squares with the biggest edge size.
|
84
|
+
max_squares_max_size_mapped = max_squares_of_clusters.map do |max_square|
|
85
|
+
max_square[:size]
|
86
|
+
end
|
87
|
+
max_squares_max_size = max_squares_max_size_mapped.max
|
88
|
+
# Select and keep only the max squares with the biggest edge size.
|
89
|
+
max_squares = max_squares_of_clusters.select do |max_square|
|
90
|
+
max_square[:size] == max_squares_max_size
|
91
|
+
end
|
92
|
+
puts max_squares # =>[{:size=>3, top_left_tile_x_y=>#<Set: {[0, 0]}>}]
|
93
|
+
```
|
94
|
+
|
95
|
+
## Development
|
96
|
+
|
97
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
98
|
+
|
99
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
100
|
+
|
101
|
+
## Contributing
|
102
|
+
|
103
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/simonneutert/slippy_tiles_scorer.
|
data/Rakefile
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlippyTilesScorer
|
4
|
+
# finds clusters in a collection/tiles_x_y of points (x, y)
|
5
|
+
class Cluster
|
6
|
+
attr_accessor :tiles_x_y
|
7
|
+
|
8
|
+
def initialize(tiles_x_y: Set.new)
|
9
|
+
@tiles_x_y = Marshal.load(Marshal.dump(tiles_x_y))
|
10
|
+
@cluster_tiles = Set.new
|
11
|
+
@visited = Set.new
|
12
|
+
@clusters = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def clusters
|
16
|
+
while @tiles_x_y.any?
|
17
|
+
start = @tiles_x_y.first
|
18
|
+
@tiles_x_y.delete(start)
|
19
|
+
@visited.add(start)
|
20
|
+
find_cluster_around(start)
|
21
|
+
end
|
22
|
+
{ clusters: @clusters, cluster_tiles: @cluster_tiles }
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def find_cluster_around(start)
|
28
|
+
cluster = Set.new
|
29
|
+
cluster.add(start)
|
30
|
+
todo = [start]
|
31
|
+
|
32
|
+
cluster = broad_search(todo, cluster)
|
33
|
+
|
34
|
+
@clusters.push(cluster) if cluster.size > 1
|
35
|
+
cluster
|
36
|
+
end
|
37
|
+
|
38
|
+
def neighbors_up_down_left_right?(neighbors)
|
39
|
+
neighbors.all? { |n| @tiles_x_y.include?(n) || @visited.include?(n) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def broad_search(todo, cluster) # rubocop:disable Metrics/MethodLength
|
43
|
+
until todo.empty?
|
44
|
+
point = todo.pop
|
45
|
+
neighbors = neighbor_points(*point)
|
46
|
+
|
47
|
+
next if neighbors.empty?
|
48
|
+
|
49
|
+
@cluster_tiles.add(point) if neighbors_up_down_left_right?(neighbors)
|
50
|
+
neighbors.each do |neighbor|
|
51
|
+
next unless @tiles_x_y.include?(neighbor)
|
52
|
+
next if @visited.include?(neighbor)
|
53
|
+
|
54
|
+
@visited.add(neighbor)
|
55
|
+
cluster.add(neighbor)
|
56
|
+
todo.push(neighbor)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
cluster
|
60
|
+
end
|
61
|
+
|
62
|
+
def neighbor_points(x, y) # rubocop:disable Naming/MethodParameterName
|
63
|
+
res = Set.new
|
64
|
+
[[x - 1, y], [x + 1, y], [x, y + 1], [x, y - 1]].each { |point| res.add(point) }
|
65
|
+
res
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SlippyTilesScorer
|
4
|
+
# finds the maximum square in a collection/tiles_x_y of points (x, y)
|
5
|
+
class MaxSquare
|
6
|
+
attr_accessor :tiles_x_y
|
7
|
+
|
8
|
+
def initialize(tiles_x_y: Set.new)
|
9
|
+
@tiles_x_y = Marshal.load(Marshal.dump(tiles_x_y))
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param min_size [Integer] The minimum size of the square, should be 3 or greater.
|
13
|
+
# As the smallest square is 3x3 and therefor a cluster tile needs to be the center.
|
14
|
+
# @return [Hash] The size of the square and the points of the square.
|
15
|
+
def max_squares(min_size: 3)
|
16
|
+
raise ArgumentError, 'min_size must be 2 or greater' if min_size < 2
|
17
|
+
|
18
|
+
result = { size: min_size, top_left_tile_x_y: Set.new }
|
19
|
+
@tiles_x_y.each do |(x, y)|
|
20
|
+
raise ArgumentError, 'x and y must be greater than or equal to 0' if x.negative? || y.negative?
|
21
|
+
|
22
|
+
steps = max_square(x: x, y: y)
|
23
|
+
track_result(result: result, steps: steps, x: x, y: y) if steps >= min_size
|
24
|
+
end
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
def max_square(x:, y:) # rubocop:disable Naming/MethodParameterName
|
29
|
+
steps = 1
|
30
|
+
steps += 1 while steps_fulfilled?(x: x, y: y, steps: steps)
|
31
|
+
steps
|
32
|
+
end
|
33
|
+
|
34
|
+
def steps_fulfilled?(x:, y:, steps:) # rubocop:disable Naming/MethodParameterName
|
35
|
+
@tiles_x_y.include?([x + steps, y + steps]) &&
|
36
|
+
(0...steps).all? do |i|
|
37
|
+
@tiles_x_y.include?([x + steps, y + i]) &&
|
38
|
+
@tiles_x_y.include?([x + i, y + steps])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def track_result(result:, steps:, x:, y:) # rubocop:disable Naming/MethodParameterName
|
45
|
+
if steps > result[:size]
|
46
|
+
result[:size] = steps
|
47
|
+
result[:top_left_tile_x_y] = Set.new
|
48
|
+
result[:top_left_tile_x_y] << [x, y]
|
49
|
+
elsif steps == result[:size]
|
50
|
+
result[:top_left_tile_x_y] << [x, y]
|
51
|
+
end
|
52
|
+
result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "slippy_tiles_scorer/version"
|
4
|
+
require_relative "./slippy_tiles_scorer/cluster"
|
5
|
+
require_relative "./slippy_tiles_scorer/max_square"
|
6
|
+
|
7
|
+
module SlippyTilesScorer
|
8
|
+
class Error < StandardError; end
|
9
|
+
|
10
|
+
# Score class to calculate the score of a collection of points (x, y)
|
11
|
+
class Score
|
12
|
+
attr_accessor :tiles_x_y
|
13
|
+
|
14
|
+
def initialize(tiles_x_y: Set.new)
|
15
|
+
@tiles_x_y = tiles_x_y
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
return true if @tiles_x_y.empty?
|
20
|
+
|
21
|
+
raise ArgumentError, "@tiles_x_y must be a Set" unless @tiles_x_y.is_a?(Set)
|
22
|
+
|
23
|
+
set_of_arrays?
|
24
|
+
set_of_arrays_of_integers?
|
25
|
+
end
|
26
|
+
|
27
|
+
def clusters(tiles_x_y: @tiles_x_y)
|
28
|
+
service = SlippyTilesScorer::Cluster.new(tiles_x_y: tiles_x_y)
|
29
|
+
result = service.clusters
|
30
|
+
result[:clusters_of_cluster_tiles] =
|
31
|
+
SlippyTilesScorer::Cluster.new(tiles_x_y: result[:cluster_tiles]).clusters[:clusters]
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def max_square(x:, y:, tiles_x_y: @tiles_x_y) # rubocop:disable Naming/MethodParameterName
|
36
|
+
SlippyTilesScorer::MaxSquare.new(tiles_x_y: tiles_x_y).max_square(x: x, y: y)
|
37
|
+
end
|
38
|
+
|
39
|
+
def max_squares(tiles_x_y: @tiles_x_y, min_size: 3)
|
40
|
+
SlippyTilesScorer::MaxSquare.new(tiles_x_y: tiles_x_y).max_squares(min_size: min_size)
|
41
|
+
end
|
42
|
+
|
43
|
+
def steps_fulfilled?(x:, y:, steps:) # rubocop:disable Naming/MethodParameterName
|
44
|
+
SlippyTilesScorer::MaxSquare.new(tiles_x_y: @tiles_x_y).steps_fulfilled?(x: x, y: y, steps: steps)
|
45
|
+
end
|
46
|
+
|
47
|
+
def visited(tiles_x_y: @tiles_x_y)
|
48
|
+
tiles_x_y.size
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def set_of_arrays?
|
54
|
+
return if @tiles_x_y.all? { |point| point.is_a?(Array) && point.size == 2 }
|
55
|
+
|
56
|
+
raise ArgumentError, "each point must be an array with two elements"
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_of_arrays_of_integers?
|
60
|
+
return if @tiles_x_y.all? { |point| point.all? { |coord| coord.is_a?(Integer) } }
|
61
|
+
|
62
|
+
raise ArgumentError, "each point must be an array with two integers"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: slippy_tiles_scorer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Simon Neutert
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-11-25 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: For a given set of slippy tiles, this gem calculates the score of the
|
14
|
+
tiles, like total tiles, clusters and max squares.
|
15
|
+
email:
|
16
|
+
- simonneutert@users.noreply.github.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- ".rubocop.yml"
|
22
|
+
- CHANGELOG.md
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- lib/slippy_tiles_scorer.rb
|
26
|
+
- lib/slippy_tiles_scorer/cluster.rb
|
27
|
+
- lib/slippy_tiles_scorer/max_square.rb
|
28
|
+
- lib/slippy_tiles_scorer/version.rb
|
29
|
+
- sig/slippy_tiles_scorer.rbs
|
30
|
+
homepage: https://github.com/simonneutert/slippy_tiles_scorer
|
31
|
+
licenses: []
|
32
|
+
metadata:
|
33
|
+
homepage_uri: https://github.com/simonneutert/slippy_tiles_scorer
|
34
|
+
source_code_uri: https://github.com/simonneutert/slippy_tiles_scorer
|
35
|
+
changelog_uri: https://github.com/simonneutert/slippy_tiles_scorer/blob/main/CHANGELOG.md
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 3.0.0
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubygems_version: 3.5.22
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: A gem to score a set of slippy tiles.
|
55
|
+
test_files: []
|