JOSM 구조 그대로 설계할 수 있을 거 같다.
예제 JOSM의 출처는 https://learnosm.org/ko/josm/start-josm/ 다.
node
<node id='-603' action='modify' visible='true' lat='0.05992435104555641' lon='0.05138006817705822'>
<tag k='name' v='Chapman's Shoes' />
<tag k='shop' v='shoes' />
</node>
node는 위 예처럼 lat, lon 좌표값을 갖고 있고, 태그들을 갖고 있다. 또 태그는 id가 없다. 태그의 경우 shop, shoes를 하나 만들고 계속 쓰기보다는 node에 종속된 느낌이다. node : tag = 1 : n 관계로 표현할 수 있다.
위 내용을 통해서 node 테이블이 필요하고, tag 테이블이 필요하다는 것을 알 수 있다.
테이블 node {
id int [pk]
created_at timestamp
coordinates geometry[Point]
}
테이블 node_tag {
node_id int [pk]
k varchar [pk]
v varchar
}
참조 관계 - node.id : tag.node_id = 1 : n
way
<way id='-715' action='modify' visible='true'>
<nd ref='-511' />
<nd ref='-513' />
<tag k='highway' v='residential' />
<tag k='name' v='Maron Lane' />
</way>
way는 node들과 tag들로 구성되어 있다.
한 node는 하나의 way에만 쓰이지 않는다. 다른 way에도 사용될 수 있다.
way를 중심으로 봤을 때 node의 순서가 있다. node1, node2 순서로 표현된다면 node1 -> node2가 되고, node2, node1 순서로 표현된다면 node2 -> node1이 된다.
따라서 node : way = n : m, way : tag = 1 : n 이란 것을 알 수 있다.
테이블 way {
id int [pk]
}
테이블 way_node {
way_id int [pk]
node_id int [pk]
order int
}
테이블 way_tag {
way_id int [pk]
k varchar [pk]
v varchar
}
참조 관계 - node : way = n : m
dbdiagram.io를 이용해서 대충 설계해 본다면 아래와 같다.
Table node {
id int [pk]
created_at timestamp
coordinates geometry[Point]
}
Table tag_node {
node_id int [pk]
k varchar [pk]
v varchar
}
Ref: tag_node.node_id > node.id
Table way {
id int [pk]
}
Table tag_way {
way_id int [pk]
k varchar [pk]
v varchar
}
Ref: tag_way.way_id > way.id
Table way_node {
way_id int [pk]
node_id int [pk]
order int
}
Ref: way_node.node_id > node.id
Ref: way_node.way_id > way.id
꼭 이렇게 설계할 필요는 없다. 최대한 JOSM을 그대로 반영해서 설계해본 것이다.
실제로 JOSM 데이터를 데이터베이스에 저장하고 다시 저장한 데이터를 JOSM으로 만들어보면 제대로 작동하지 않을 수도 있다.
위 설계대로 GeoDjango에 모델 파일을 만들어보면 아래와 같다.
from django.contrib.gis.db import models
class Node(models.Model):
coordinates = models.PointField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class TagNode(models.Model):
k = models.CharField()
v = models.CharField()
node = models.ForeignKey(Node, on_delete=models.CASCADE)
class Meta:
unique_together = ('node', 'k',)
class Way(models.Model):
nodes = models.ManyToManyField(Node, through='WayNode', through_fields=('way', 'node'))
class WayNode(models.Model):
way = models.ForeignKey(Way, on_delete=models.CASCADE)
node = models.ForeignKey(Node, on_delete=models.CASCADE)
order = models.IntegerField()
class Meta:
unique_together = ('way', 'node')
class TagWay(models.Model):
k = models.CharField()
v = models.CharField()
way = models.ForeignKey(Way, on_delete=models.CASCADE)
class Meta:
unique_together = ('way', 'k',)
OpenStreetMap 깃허브 저장소의 설계 관련 파일
openstreetmap-website 저장소에 가면 데이터베이스 관련 파일도 볼 수 있다.
출처: https://github.com/openstreetmap/openstreetmap-website/blob/master/db/migrate/001_create_osm_db.rb
class CreateOsmDb < ActiveRecord::Migration[4.2]
def self.up
create_table "current_nodes", :id => false do |t|
t.column "id", :bigint, :null => false
t.column "latitude", :float, :limit => 53
t.column "longitude", :float, :limit => 53
t.column "user_id", :bigint
t.column "visible", :boolean
t.column "tags", :text, :default => "", :null => false
t.column "timestamp", :datetime
end
add_index "current_nodes", ["id"], :name => "current_nodes_id_idx"
add_index "current_nodes", %w[latitude longitude], :name => "current_nodes_lat_lon_idx"
add_index "current_nodes", ["timestamp"], :name => "current_nodes_timestamp_idx"
create_table "current_segments", :id => false do |t|
t.column "id", :bigint, :null => false
t.column "node_a", :bigint
t.column "node_b", :bigint
t.column "user_id", :bigint
t.column "visible", :boolean
t.column "tags", :text, :default => "", :null => false
t.column "timestamp", :datetime
end
add_index "current_segments", %w[id visible], :name => "current_segments_id_visible_idx"
add_index "current_segments", ["node_a"], :name => "current_segments_a_idx"
add_index "current_segments", ["node_b"], :name => "current_segments_b_idx"
create_table "current_way_segments", :id => false do |t|
t.column "id", :bigint
t.column "segment_id", :bigint
t.column "sequence_id", :bigint
end
add_index "current_way_segments", ["segment_id"], :name => "current_way_segments_seg_idx"
add_index "current_way_segments", ["id"], :name => "current_way_segments_id_idx"
create_table "current_way_tags", :id => false do |t|
t.column "id", :bigint
t.column "k", :string, :default => "", :null => false
t.column "v", :string, :default => "", :null => false
end
add_index "current_way_tags", ["id"], :name => "current_way_tags_id_idx"
add_index "current_way_tags", "v", :name => "current_way_tags_v_idx"
create_table "current_ways", :id => false do |t|
t.column "id", :bigserial, :primary_key => true, :null => false
t.column "user_id", :bigint
t.column "timestamp", :datetime
t.column "visible", :boolean
end
create_table "diary_entries", :id => false do |t|
t.column "id", :bigserial, :primary_key => true, :null => false
t.column "user_id", :bigint, :null => false
t.column "title", :string
t.column "body", :text
t.column "created_at", :datetime
t.column "updated_at", :datetime
end
create_table "friends", :id => false do |t|
t.column "id", :bigserial, :primary_key => true, :null => false
t.column "user_id", :bigint, :null => false
t.column "friend_user_id", :bigint, :null => false
end
add_index "friends", ["friend_user_id"], :name => "user_id_idx"
create_table "gps_points", :id => false do |t|
t.column "altitude", :float
t.column "user_id", :integer
t.column "trackid", :integer
t.column "latitude", :integer
t.column "longitude", :integer
t.column "gpx_id", :integer
t.column "timestamp", :datetime
end
add_index "gps_points", %w[latitude longitude user_id], :name => "points_idx"
add_index "gps_points", ["user_id"], :name => "points_uid_idx"
add_index "gps_points", ["gpx_id"], :name => "points_gpxid_idx"
create_table "gpx_file_tags", :id => false do |t|
t.column "gpx_id", :bigint, :default => 0, :null => false
t.column "tag", :string
t.column "id", :bigserial, :primary_key => true, :null => false
end
add_index "gpx_file_tags", ["gpx_id"], :name => "gpx_file_tags_gpxid_idx"
create_table "gpx_files", :id => false do |t|
t.column "id", :bigserial, :primary_key => true, :null => false
t.column "user_id", :bigint
t.column "visible", :boolean, :default => true, :null => false
t.column "name", :string, :default => "", :null => false
t.column "size", :bigint
t.column "latitude", :float, :limit => 53
t.column "longitude", :float, :limit => 53
t.column "timestamp", :datetime
t.column "public", :boolean, :default => true, :null => false
t.column "description", :string, :default => ""
t.column "inserted", :boolean
end
add_index "gpx_files", ["timestamp"], :name => "gpx_files_timestamp_idx"
add_index "gpx_files", %w[visible public], :name => "gpx_files_visible_public_idx"
create_table "gpx_pending_files", :id => false do |t|
t.column "originalname", :string
t.column "tmpname", :string
t.column "user_id", :bigint
end
create_table "messages", :id => false do |t|
t.column "id", :bigserial, :primary_key => true, :null => false
t.column "user_id", :bigint, :null => false
t.column "from_user_id", :bigint, :null => false
t.column "from_display_name", :string, :default => ""
t.column "title", :string
t.column "body", :text
t.column "sent_on", :datetime
t.column "message_read", :boolean, :default => false
t.column "to_user_id", :bigint, :null => false
end
add_index "messages", ["from_display_name"], :name => "from_name_idx"
create_table "meta_areas", :id => false do |t|
t.column "id", :bigserial, :primary_key => true, :null => false
t.column "user_id", :bigint
t.column "timestamp", :datetime
end
create_table "nodes", :id => false do |t|
t.column "id", :bigint
t.column "latitude", :float, :limit => 53
t.column "longitude", :float, :limit => 53
t.column "user_id", :bigint
t.column "visible", :boolean
t.column "tags", :text, :default => "", :null => false
t.column "timestamp", :datetime
end
add_index "nodes", ["id"], :name => "nodes_uid_idx"
add_index "nodes", %w[latitude longitude], :name => "nodes_latlon_idx"
create_table "segments", :id => false do |t|
t.column "id", :bigint
t.column "node_a", :bigint
t.column "node_b", :bigint
t.column "user_id", :bigint
t.column "visible", :boolean
t.column "tags", :text, :default => "", :null => false
t.column "timestamp", :datetime
end
add_index "segments", ["node_a"], :name => "street_segments_nodea_idx"
add_index "segments", ["node_b"], :name => "street_segments_nodeb_idx"
add_index "segments", ["id"], :name => "street_segment_uid_idx"
create_table "users", :id => false do |t|
t.column "email", :string
t.column "id", :bigserial, :primary_key => true, :null => false
t.column "token", :string
t.column "active", :integer, :default => 0, :null => false
t.column "pass_crypt", :string
t.column "creation_time", :datetime
t.column "timeout", :datetime
t.column "display_name", :string, :default => ""
t.column "preferences", :text
t.column "data_public", :boolean, :default => false
t.column "description", :text, :default => "", :null => false
t.column "home_lat", :float, :limit => 53, :default => 1
t.column "home_lon", :float, :limit => 53, :default => 1
t.column "within_lon", :float, :limit => 53
t.column "within_lat", :float, :limit => 53
t.column "home_zoom", :integer, :limit => 2, :default => 3
end
add_index "users", ["email"], :name => "users_email_idx"
add_index "users", ["display_name"], :name => "users_display_name_idx"
create_table "way_segments", :id => false do |t|
t.column "id", :bigint, :default => 0, :null => false
t.column "segment_id", :integer
t.column "version", :bigint, :default => 0, :null => false
t.column "sequence_id", :bigint, :null => false
end
add_primary_key "way_segments", %w[id version sequence_id]
create_table "way_tags", :id => false do |t|
t.column "id", :bigint, :default => 0, :null => false
t.column "k", :string
t.column "v", :string
t.column "version", :bigint
end
add_index "way_tags", %w[id version], :name => "way_tags_id_version_idx"
create_table "ways", :id => false do |t|
t.column "id", :bigint, :default => 0, :null => false
t.column "user_id", :bigint
t.column "timestamp", :datetime
t.column "version", :bigint, :null => false
t.column "visible", :boolean, :default => true
end
add_primary_key "ways", %w[id version]
add_index "ways", ["id"], :name => "ways_id_version_idx"
end
def self.down; end
end