/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.globaltime; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.TimeZone; /** * A class representing a city, with an associated position, time zone name, * and raw offset from UTC. */ public class City implements Comparable<City> { private static Map<String,City> cities = new HashMap<String,City>(); private static City[] citiesByRawOffset; private String name; private String timeZoneID; private TimeZone timeZone = null; private int rawOffset; private float latitude, longitude; private float x, y, z; /** * Loads the city database. The cities must be stored in order by raw * offset from UTC. */ public static void loadCities(InputStream is) throws IOException { DataInputStream dis = new DataInputStream(is); int numCities = dis.readInt(); citiesByRawOffset = new City[numCities]; byte[] buf = new byte[24]; for (int i = 0; i < numCities; i++) { String name = dis.readUTF(); String tzid = dis.readUTF(); dis.read(buf); // The code below is a faster version of: // int rawOffset = dis.readInt(); // float latitude = dis.readFloat(); // float longitude = dis.readFloat(); // float cx = dis.readFloat(); // float cy = dis.readFloat(); // float cz = dis.readFloat(); int rawOffset = (buf[ 0] << 24) | ((buf[ 1] & 0xff) << 16) | ((buf[ 2] & 0xff) << 8) | (buf[ 3] & 0xff); int ilat = (buf[ 4] << 24) | ((buf[ 5] & 0xff) << 16) | ((buf[ 6] & 0xff) << 8) | (buf[ 7] & 0xff); int ilon = (buf[ 8] << 24) | ((buf[ 9] & 0xff) << 16) | ((buf[10] & 0xff) << 8) | (buf[11] & 0xff); int icx = (buf[12] << 24) | ((buf[13] & 0xff) << 16) | ((buf[14] & 0xff) << 8) | (buf[15] & 0xff); int icy = (buf[16] << 24) | ((buf[17] & 0xff) << 16) | ((buf[18] & 0xff) << 8) | (buf[19] & 0xff); int icz = (buf[20] << 24) | ((buf[21] & 0xff) << 16) | ((buf[22] & 0xff) << 8) | (buf[23] & 0xff); float latitude = Float.intBitsToFloat(ilat); float longitude = Float.intBitsToFloat(ilon); float cx = Float.intBitsToFloat(icx); float cy = Float.intBitsToFloat(icy); float cz = Float.intBitsToFloat(icz); City city = new City(name, tzid, rawOffset, latitude, longitude, cx, cy, cz); cities.put(name, city); citiesByRawOffset[i] = city; } } /** * Returns the cities, ordered by name. */ public static City[] getCitiesByName() { City[] ocities = new City[cities.size()]; Iterator<City> iter = cities.values().iterator(); int idx = 0; while (iter.hasNext()) { ocities[idx++] = iter.next(); } Arrays.sort(ocities); return ocities; } /** * Returns the cities, ordered by offset, accounting for summer/daylight * savings time. This requires reading the entire time zone database * behind the scenes. */ public static City[] getCitiesByOffset() { City[] ocities = new City[cities.size()]; Iterator<City> iter = cities.values().iterator(); int idx = 0; while (iter.hasNext()) { ocities[idx++] = iter.next(); } Arrays.sort(ocities, new Comparator() { public int compare(Object o1, Object o2) { long now = System.currentTimeMillis(); City c1 = (City)o1; City c2 = (City)o2; TimeZone tz1 = c1.getTimeZone(); TimeZone tz2 = c2.getTimeZone(); int off1 = tz1.getOffset(now); int off2 = tz2.getOffset(now); if (off1 == off2) { float dlat = c2.getLatitude() - c1.getLatitude(); if (dlat < 0.0f) return -1; if (dlat > 0.0f) return 1; return 0; } return off1 - off2; } }); return ocities; } /** * Returns the cities, ordered by offset, accounting for summer/daylight * savings time. This does not require reading the time zone database * since the cities are pre-sorted. */ public static City[] getCitiesByRawOffset() { return citiesByRawOffset; } /** * Returns an Iterator over all cities, in raw offset order. */ public static Iterator<City> iterator() { return cities.values().iterator(); } /** * Returns the total number of cities. */ public static int numCities() { return cities.size(); } /** * Constructs a city with the given name, time zone name, raw offset, * latitude, longitude, and 3D (X, Y, Z) coordinate. */ public City(String name, String timeZoneID, int rawOffset, float latitude, float longitude, float x, float y, float z) { this.name = name; this.timeZoneID = timeZoneID; this.rawOffset = rawOffset; this.latitude = latitude; this.longitude = longitude; this.x = x; this.y = y; this.z = z; } public String getName() { return name; } public TimeZone getTimeZone() { if (timeZone == null) { timeZone = TimeZone.getTimeZone(timeZoneID); } return timeZone; } public float getLongitude() { return longitude; } public float getLatitude() { return latitude; } public float getX() { return x; } public float getY() { return y; } public float getZ() { return z; } public float getRawOffset() { return rawOffset / 3600000.0f; } public int getRawOffsetMillis() { return rawOffset; } /** * Returns this city's offset from UTC, taking summer/daylight savigns * time into account. */ public float getOffset() { long now = System.currentTimeMillis(); if (timeZone == null) { timeZone = TimeZone.getTimeZone(timeZoneID); } return timeZone.getOffset(now) / 3600000.0f; } /** * Compares this city to another by name. */ public int compareTo(City o) { return name.compareTo(o.name); } }