import java.util.*; import java.io.*; import java.nio.*; import java.nio.charset.*; import java.nio.file.*; import java.awt.*; import java.awt.image.BufferedImage; public class OSM { final static double E7 = 10000000.0; final static double B32 = 1024*1024*1024; // (2 の 30乗) static double Lon2X(double lon, int zoom) { return (lon + 180.0) / 360.0 * (1<> 32); } static int getY(long xy) { return (int)(xy & 0xffffffffL); } static long makeXY(int x, int y) { return (((long)x)<<32) | (y & 0xffffffffL); } // String sId = getAttr(" id=", line); static String getAttr(String name, String s) { int ixBgn = s.indexOf(name); if (ixBgn < 0) return null; ixBgn += name.length() + 1; // '"' の分 int ix = ixBgn; while (ix < s.length() && s.charAt(ix) != '"' && s.charAt(ix) != '\'') { ix++; } return s.substring(ixBgn, ix); } // 文字列は 末尾に '\n' を付加して、順に bbWords に格納する。その先頭位置を文字列のコードとする。 // mapWords に登録し、同じ文字列の登録は無くする。 static HashMap mapWords = new HashMap(); static char[] caWordsUTF16 = new char[32*1024*1024]; // 64MB static int ixWordsIni; // bbWordsUtf8 の初期サイズ static int ixWords; static int wordId(String word) { if (mapWords.containsKey(word)) { return mapWords.get(word); } int wid = ixWords; mapWords.put(word, wid); char[] chrs = word.toCharArray(); System.arraycopy(chrs, 0, caWordsUTF16, ixWords, chrs.length); ixWords += chrs.length; caWordsUTF16[ixWords++] = '\n'; return wid; } static void writeWords(String file) { if (ixWords == ixWordsIni) return; File f = new File(file); try (DataOutputStream dos = new DataOutputStream( new BufferedOutputStream(new FileOutputStream(file)))) { for (int n = 0; n < ixWords; n++) { dos.writeChar(caWordsUTF16[n]); } System.out.printf("writeWords %d語\n", mapWords.size()); } catch (Exception e) { e.printStackTrace(); } } // UTF-16コード static void readWords(String file) { if (!Files.exists(Paths.get(file))) { ixWords = ixWordsIni = 0; return; } File f = new File(file); ixWords = ixWordsIni = (int) f.length()/2; try (DataInputStream dis = new DataInputStream( new BufferedInputStream(new FileInputStream(file)))) { int code = 0; // 文字コード for (int ix = 0; ix < ixWordsIni; ix++) { char c = dis.readChar(); if (c == '\n') { String word = new String(caWordsUTF16, code, ix-code); //System.out.printf("%d <%s>\n", code, word); mapWords.put(word, code); code = ix+1; // 次の word のコード } caWordsUTF16[ix] = c; } System.out.printf("readWords %d語\n", mapWords.size()); } catch (Exception e) { e.printStackTrace(); } } int type; // 0: point, 1: line, 2: polygon, 3: multipolygon int head; // 各種フラグを含む int rid; // 廃止予定 long id; // osm_id int[] multi; // multipolygon管理 int[] xs, ys; String[] tags; int xmin, ymin, xmax, ymax; int xc, yc; float way_area = 0; int admin_level = -1; public OSM(int type, long id, int[] multi, long[] lonlats, java.util.List listTags) { this(type, id, multi, lonlats, listTags.toArray(new String[0])); } public OSM(int type, long id, int[] multi, long[] lonlats, String[] tags) { this.type = type; this.head = type; this.id = id; // osm_id this.multi = multi; this.tags = tags;//listTags.toArray(new String[0]); if (type == 2) { // 単純polygon double dWay_area = OSMLib.calcWayArea(lonlats); if (dWay_area < 0) reverse(lonlats); // 時計回りに変換する way_area = (float)Math.abs(dWay_area); } else if (type == 3) { // multipolygon: outer polygon の面積 way_area = (float)Math.abs(OSMLib.calcWayArea(lonlats, 0, multi[1])); } if (multi != null) { if (multi[0] < Relation.minPolys) Relation.minPolys = multi[0]; if (multi[0] > Relation.maxPolys) Relation.maxPolys = multi[0]; } xs = new int[lonlats.length]; ys = new int[lonlats.length]; for (int n = 0; n < lonlats.length; n++) { int lon = getX(lonlats[n]); int lat = getY(lonlats[n]); xs[n] = (int)(Lon2X(lon/E7, 0) * B32); ys[n] = (int)(Lat2Y(lat/E7, 0) * B32); } int nEnd = multi==null ? lonlats.length : multi[0]; xmin = xmax = xs[0]; ymin = ymax = ys[0]; if (lonlats.length >= 2) { // line/polygon for (int n = 1; n < nEnd; n++) { if (xs[n] < xmin) xmin = xs[n]; if (xs[n] > xmax) xmax = xs[n]; if (ys[n] < ymin) ymin = ys[n]; if (ys[n] > ymax) ymax = ys[n]; } } if (type >= 2) { this.head |= 0x10; // 中心座標あり long center = OSMLib.getCenter(lonlats, nEnd); int lon = getX(center); int lat = getY(center); xc = (int)(Lon2X(lon/E7, 0) * B32); // 上位4バイトは 0 yc = (int)(Lat2Y(lat/E7, 0) * B32); // 上位4バイトは 0 } } byte[] toBinaryRecord() { int flags = type==0 ? arbiter(this) : 0; // OSMバイナリレコードサイズの算出 int size = 9; // head, rec_length, x0, y0, x1, y1, rec_id, osm_id, if (type >= 2) { size += 1; // way_area } if ((head & 0x10) != 0) { size += 2; // xc, yc } size += tags.length * 2; // {key,val}+ if (flags != 0) { size += 2; } if (type == 1 || type == 2) { size += 1 + xs.length * 2; // num_nodes, {x, y}* } else if (type == 3) { size += multi.length + xs.length * 2; } byte[] ba = new byte[size*4]; ByteBuffer bb = ByteBuffer.wrap(ba); bb.putInt(head); bb.putInt(size*4 - 8); // head, length は含まない bb.putInt(xmin); // (2) bb.putInt(ymin); // (3) bb.putInt(xmax); // (4) bb.putInt(ymax); // (5) bb.putInt(rid); // (6) rid bb.putLong(id); // (7) osm_id if (type >= 2) { bb.putFloat(way_area); // (8) } if ((head & 0x10) != 0) { bb.putInt(xc); // (9) bb.putInt(yc); // (10) } encodeTags(bb, flags); // tag if (multi == null) { if (type >= 1) bb.putInt(xs.length); } else { for (int n = 0; n < multi.length; n++) { int nodes = multi[n]; if (n < multi.length - 1) { nodes |= 0x80000000; // 継続フラグ } bb.putInt(nodes); } } if (type > 0) { for (int n = 0; n < xs.length; n++) { bb.putInt(xs[n]); bb.putInt(ys[n]); } } if (bb.remaining() > 0) { System.out.printf("Error: 残り=%d\n", bb.remaining()); } return ba; } void encodeTags(ByteBuffer bb, int flags) { String name = null; boolean sidewalk = false; for (int n = 0; n < tags.length; n++) { String tag = tags[n]; if (tag.startsWith("name=")) { name = tag.substring(5); } if (tag.equals("footway=sidewalk")) { sidewalk = true; } } for (int n = 0; n < tags.length; n++) { String tag = tags[n]; if (name != null && tag.equals("amenity=doctors") && name.contains("鍼灸")) { tag = "shop=massage"; // 2022.9.20 } else if (name != null && tag.equals("amenity=hospital") && name.endsWith("医院") && !name.endsWith("附属順天堂医院")) { if (name.contains("歯科")) tag = "amenity=dentist"; else tag = "amenity=doctors"; // 2022.10.6 } else if (name != null && tag.equals("shop=supermarket") && name.contains("まいばすけっと")) { tag = "shop=convenience"; // 2022.10.7 } if (tag.equals("highway=footway") && !sidewalk) { tag = "highway=path"; // 2022.11.20 } String[] kv = tag.split("="); /*if (kv.length < 2) { System.out.printf("error tag: '%s'\n", tag); continue; }*/ if (kv[1].equals("private")) kv[1] = "_private"; // 2022.6.11 else if (kv[1].equals("public")) kv[1] = "_public"; // 2022.6.11 kv[0] = kv[0].replace(":", "_"); // 2022.5.15 int key = 0; if (OSMParser.mapKeys.containsKey(kv[0])) { key = OSMParser.mapKeys.get(kv[0]); } long val = 0; if (key >= Tag.Key.ele.ordinal()) { try { double dval = Double.parseDouble(kv[1]); val = (long)(dval*10); } catch (Exception ex) { } } else if (key >= Tag.Key.admin_level.ordinal()) { try { val = Integer.parseInt(kv[1]); } catch (Exception ex) { } } else if (key >= Tag.Key.name.ordinal()) { // 文字列型タグ String text = kv[1]; text = text.replace("'", "'").replace("&", "&"); text = text.replace("\n", " "); StringBuffer sb = new StringBuffer(); char[] charArray = text.toCharArray(); for (char ch : charArray) { sb.append((ch==27||ch==165) ? '¥' : ch); } text = sb.toString(); val = wordId(text); } else if (OSMParser.mapVals.containsKey(kv[1])) { val = OSMParser.mapVals.get(kv[1]); } if (n < tags.length - 1 || flags != 0) { key |= 0x80000000; // 後続タグがある } bb.putInt(key); bb.putInt((int)val); } if (flags != 0) { // 特殊タグ bb.putInt(Tag.Key.placename_flags.ordinal()); bb.putInt(flags); } } static void reverse(long[] v) { for (int i = 0; i < v.length/2; i++) { long temp = v[i]; v[i] = v[v.length - i - 1]; v[v.length - i - 1] = temp; } } static byte[] ba = new byte[16*1024*1024]; static HashSet setMainPlaces = new HashSet<>( Arrays.asList("province", "city", "suburb", "town", "village")); static Rectangle[][] rects = new Rectangle[32][256*1024]; static int[] nRect = new int[32]; static FontMetrics fontmetrics = null; static int arbiter(OSM osm) { String place = null; String name = null; for (String kv : osm.tags) { if (kv.startsWith("place=")) { place = kv.substring(6); } else if (kv.startsWith("name=")) { name = kv.substring(5); } } if (place == null || name == null) return 0; if (place.equals("suburb") && !name.endsWith("区")) return 0; if (!setMainPlaces.contains(place)) return 0; if (fontmetrics == null) { BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_BGR); Graphics graphics = bi.createGraphics(); Font font = new Font("Tahoma", Font.PLAIN, 14); graphics.setFont(font); fontmetrics = graphics.getFontMetrics(); } String[] texts = OSMLib.Wrap(name).split("\n"); int lines = texts.length; int h = fontmetrics.getHeight(); int w = 0; for (String s : texts) { int wi = fontmetrics.stringWidth(s); if (wi > w) w = wi; } // 一番長い行の幅を求める int minzoom; if (place.equals("province")) minzoom = 5; else if (place.equals("city")) minzoom = 7; else if (place.equals("suburb")) minzoom = 9; else if (place.equals("town")) minzoom = 9; else if (place.equals("city")) minzoom = 10; else minzoom = 11; int flags = 0; // zoom毎の描画のon/offを記録する for (int zoom = minzoom; zoom <= 22; zoom++) { double fact = 256.0 / (1 << (30-zoom)); double x = osm.xmin * fact;// 描画地名候補の中心座標(画素単位) double y = osm.ymin * fact; float Fact = zoom <= 7 ? 1.2f : 2; float dy = 0; // 将来は dy > 0 があるかもしれない int y1 = (int)(y - h * lines * Fact / 2); if (dy > 0) y1 += h * lines / 2 + dy; Rectangle rect = new Rectangle((int) (x - w * Fact/2), y1, (int)(w * Fact), (int) (h * lines * Fact)); boolean intersect = false; for (int n = 0; n < nRect[zoom]; n++) { Rectangle r = rects[zoom][n]; if (rect.intersects(r)) { intersect = true; break; } } //System.out.printf("%d: (%d %d %d %d) %s %b\n", zoom, rect.x, rect.y, rect.width, rect.height, name, intersect); if (intersect) continue; // 描画しない rects[zoom][nRect[zoom]++] = rect; flags |= (1 << zoom); //System.out.printf("%d: (%d %d %d %d) %s %b %x\n", zoom, rect.x, rect.y, rect.width, rect.height, name, intersect, flags); //if (zoom < 17) System.out.printf("zoom=%d (%.0f %.0f) %s\n", zoom, x, y, name); } return flags; } }