/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2013, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.data.mapinfo; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.internal.InternalUtilities; import org.apache.sis.referencing.datum.BursaWolfParameters; import org.apache.sis.referencing.datum.DefaultGeodeticDatum; import org.apache.sis.referencing.datum.DefaultPrimeMeridian; import org.opengis.referencing.datum.Ellipsoid; import org.opengis.referencing.datum.GeodeticDatum; import org.opengis.referencing.datum.PrimeMeridian; import org.opengis.util.FactoryException; import java.util.AbstractMap; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.sis.measure.Units; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.referencing.IdentifiedObjects; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.ComparisonMode; import org.apache.sis.util.Utilities; /** * A class which binds mapinfo datum codes with equivalent epsg code. * * @author Alexis Manin (Geomatys) * Date : 06/03/13 */ public final class DatumIdentifier { /** A map containing mapinfo datum codes as key, and EPSG code as value. */ private static final Map<Integer, Integer> DATUM_TABLE = new HashMap<>(); /** * For datums we don't have EPSG equivalent, we get a list of Bursa Wolf parameters for each one (to WGS 84 transformation). * The key is the MapInfo code of the datum. The value is pair of String double array, where the String is the datum name, * and the double array is composed of : * 0 --> Ellipsoid code (MapInfo code) * 1 --> Shift on X axis (meters) * 2 --> Shift on Y axis (meters) * 3 --> Shift on Z axis (meters) * 4 --> Rotation on X axis (arc second) * 5 --> Rotation on Y axis (arc second) * 6 --> Rotation on Z axis (arc second) * 7 --> Scaling (in part per million) * 8 --> Prime meridian longitude to greenwich */ private static final Map<Integer, Map.Entry<String, double[]> > HANDED_DATUM_TABLE = new HashMap<>(); private static AbstractMap.SimpleImmutableEntry<String, double[]> buildValue(String name, double... values) { return new AbstractMap.SimpleImmutableEntry<>(name, values); } /** * Return the EPSG code of the datum pointed by given MapInfo code. * @param mapinfoDatumCode The code of the datum to retrieve, as given by MapInfo. * @return EPSG code of a referenced datum, or null if no equivalent can be found. Zero will be return if mapinfo * code refer to user custom datum. */ public static Integer getEPSGDatumCode(Integer mapinfoDatumCode) { ArgumentChecks.ensureNonNull("MapInfo Datum Code", mapinfoDatumCode); return DATUM_TABLE.get(mapinfoDatumCode); } /** * Search a MIF code for the given EPSG datum code. * @param epsgCode The EPSG code which represents the wanted datum. * @return The MIF code for the found datum, or -1 if no equivalent can be found. */ public static int getMIFCodeFromEPSG(int epsgCode) { for(Map.Entry<Integer, Integer> pair : DATUM_TABLE.entrySet()) { if(pair.getValue().equals(epsgCode)) { return pair.getKey(); } } return -1; } /** * Build a MIF datum from a Geotk one. If we can find an existing one MapInfo datums, we just return its code. * Otherwise, we must build a custom datum using Bursa Wolf transformation (to WGS 84). * @param source The datum to write. * @return A String representing the given datum as MapInfo needs it. * @throws FactoryException If we don't find a per-existing code, and we cannot retrieve datum ellipsoid. * @throws DataStoreException Same conditions as FactoryException. */ public static String getMIFDatum(GeodeticDatum source) throws FactoryException, DataStoreException { StringBuilder builder = new StringBuilder(); Integer epsgCode = IdentifiedObjects.lookupEPSG(source); if(epsgCode == null) { epsgCode = -1; } int mifCode = getMIFCodeFromEPSG(epsgCode); // If we can't find a code matching, we use the datum ellipsoid / Bursa Wolf parameters (to WGS 84) to search in // the handed-built list, or create a custom datum. if(mifCode < 0) { if(!(source instanceof DefaultGeodeticDatum)) { throw new DataStoreException("Unsupported datum type."); } double primeShift = 0; int customCode = 999; if(source.getPrimeMeridian() != CommonCRS.WGS84.primeMeridian()) { customCode = 9999; primeShift = source.getPrimeMeridian().getGreenwichLongitude(); } int ellipsoidCode = EllipsoidIdentifier.getMIFCode(source.getEllipsoid()); if(ellipsoidCode < 0) { throw new DataStoreException("We're unable to find an ellipsoid for source datum."); } BursaWolfParameters bwParams = null; final GeodeticDatum targetDatum = CommonCRS.WGS84.datum(); for (BursaWolfParameters param : ((DefaultGeodeticDatum) source).getBursaWolfParameters()) { if (Utilities.deepEquals(targetDatum, param.getTargetDatum(), ComparisonMode.IGNORE_METADATA)) { bwParams = param; break; } } if(bwParams == null) { bwParams = new BursaWolfParameters(targetDatum, null); } // search in the handed-built list double[] comparisonParams = new double[]{ ellipsoidCode, bwParams.tX, bwParams.tY, bwParams.tZ, bwParams.rX, bwParams.rY, bwParams.rZ, bwParams.dS, primeShift}; for(Map.Entry<Integer, Map.Entry<String, double[]>> entry : HANDED_DATUM_TABLE.entrySet()) { if(Arrays.equals(comparisonParams, entry.getValue().getValue())) { mifCode = entry.getKey(); break; } } if(mifCode >= 0) { builder.append(mifCode); } else { // build a custom CRS. builder .append(customCode).append(", ") .append(ellipsoidCode).append(", ") .append(bwParams.tX).append(", ") .append(bwParams.tY).append(", ") .append(bwParams.tZ).append(", ") .append(bwParams.rX).append(", ") .append(bwParams.rY).append(", ") .append(bwParams.rZ).append(", ") .append(bwParams.dS); if(primeShift != 0) { builder.append(", ").append(primeShift); } } } else { builder.append(mifCode); } return builder.toString(); } /** * Build a Geodetic datum from a given MIF code (should not be 999 or 9999) * @param datumCode The code to find a matching datum for. * @return A datum matching the given MapInfo code, or null otherwise (Ex: if input code is 999). * @throws FactoryException If we get a problem while browsing datum database. * @throws DataStoreException If there's a problem building our datum. */ public static GeodeticDatum getDatumFromMIFCode(int datumCode) throws FactoryException, DataStoreException { GeodeticDatum datum = null; final Integer epsgDatum = DatumIdentifier.getEPSGDatumCode(datumCode); if(epsgDatum != null) { datum = ProjectionUtils.getDatumFactory().createGeodeticDatum(epsgDatum.toString()); } else { if(HANDED_DATUM_TABLE.containsKey(datumCode)) { Map.Entry<String, double[]> datumInfo = HANDED_DATUM_TABLE.get(datumCode); datum = buildCustomDatum(datumInfo.getKey(), datumInfo.getValue()); } } return datum; } /** * Build a Geodetic datum from a MapInfo ellipsoid code and Bursa Wolf parameters to WGS84. * @param name The name to give to built datum. If null, a name will be built from ellipsoid name. * @param parameters The parameters for datum making. Order is : * 0 --> Ellipsoid code (MapInfo code) * 1 --> Shift on X axis (meters) * 2 --> Shift on Y axis (meters) * 3 --> Shift on Z axis (meters) * Next are facultative : * 4 --> Rotation on X axis (arc second) * 5 --> Rotation on Y axis (arc second) * 6 --> Rotation on Z axis (arc second) * 7 --> Scaling (in part per million) * 8 --> Prime meridian longitude to greenwich * @return A geodetic datum object. * @throws DataStoreException If there's not enough parameters. * @throws FactoryException If we've got a problem while ellipsoid building. */ public static GeodeticDatum buildCustomDatum(String name, double[] parameters) throws DataStoreException, FactoryException { BursaWolfParameters bwParams = new BursaWolfParameters(CommonCRS.WGS84.datum(), null); if(parameters.length < 1) { throw new DataStoreException("There's not enough parameters to build a valid datum. An ellipsoid code is required."); } // Get the ellipsoid int ellipsoidCode = (int) parameters[0]; Ellipsoid ellipse = EllipsoidIdentifier.getEllipsoid(ellipsoidCode); if(parameters.length > 3) { bwParams.tX = parameters[1]; bwParams.tY = parameters[2]; bwParams.tZ = parameters[3]; } if(parameters.length > 7) { bwParams.rX = parameters[4]; bwParams.rY = parameters[5]; bwParams.rZ = parameters[6]; bwParams.dS = parameters[7]; } // If we've got 9 parameters, the datum is not based on Greenwich meridian. PrimeMeridian pMeridian = CommonCRS.WGS84.primeMeridian(); if(parameters.length > 8 && !InternalUtilities.epsilonEqual(parameters[8], 0)) { pMeridian = new DefaultPrimeMeridian(Collections.singletonMap(PrimeMeridian.NAME_KEY, "Greenwich" + ((parameters[8] > 0) ? "+" + parameters[8] : parameters[8])), parameters[8], Units.DEGREE); } final Map<String, Object> properties = new HashMap<>(); properties.put(GeodeticDatum.NAME_KEY, (name != null)? name : ellipse.getName().getCode()+"_custom_datum"); properties.put(DefaultGeodeticDatum.BURSA_WOLF_KEY, bwParams); return new DefaultGeodeticDatum(properties, ellipse, pMeridian); } //Fill table static { DATUM_TABLE.put(1, 6201); DATUM_TABLE.put(2, 6205); DATUM_TABLE.put(3, 6204); DATUM_TABLE.put(4, 6708); DATUM_TABLE.put(5, 6209); DATUM_TABLE.put(6, 6210); DATUM_TABLE.put(7, 6712); DATUM_TABLE.put(8, 6709); DATUM_TABLE.put(9, 6707); DATUM_TABLE.put(10, 6710); DATUM_TABLE.put(11, 6711); DATUM_TABLE.put(12, 6202); DATUM_TABLE.put(13, 6203); DATUM_TABLE.put(14, 6714); DATUM_TABLE.put(15, 6216); DATUM_TABLE.put(16, 6218); DATUM_TABLE.put(17, 6221); DATUM_TABLE.put(18, 6716); DATUM_TABLE.put(19, 6222); DATUM_TABLE.put(20, 6717); DATUM_TABLE.put(21, 6223); DATUM_TABLE.put(22, 6672); DATUM_TABLE.put(23, 6224); DATUM_TABLE.put(24, 6225); DATUM_TABLE.put(25, 6813); // 26 DATUM_TABLE.put(27, 6719); DATUM_TABLE.put(28, 6230); DATUM_TABLE.put(29, 6668); DATUM_TABLE.put(30, 6684); DATUM_TABLE.put(31, 6272); DATUM_TABLE.put(32, 6036); DATUM_TABLE.put(33, 6019); DATUM_TABLE.put(34, 6675); // 35 DATUM_TABLE.put(36, 6254); DATUM_TABLE.put(37, 6658); DATUM_TABLE.put(38, 6738); DATUM_TABLE.put(39, 6236); DATUM_TABLE.put(40, 6131); // 41 DATUM_TABLE.put(42, 6300); DATUM_TABLE.put(43, 6724); DATUM_TABLE.put(44, 6725); DATUM_TABLE.put(45, 6244); DATUM_TABLE.put(46, 6698); DATUM_TABLE.put(47, 6245); DATUM_TABLE.put(48, 6726); DATUM_TABLE.put(49, 6251); DATUM_TABLE.put(50, 6253); DATUM_TABLE.put(50, 6253); // 51 DATUM_TABLE.put(52, 6256); DATUM_TABLE.put(53, 6616); DATUM_TABLE.put(54, 6262); DATUM_TABLE.put(55, 6261); DATUM_TABLE.put(56, 6727); DATUM_TABLE.put(57, 6263); // 58 // 59 DATUM_TABLE.put(60, 6270); DATUM_TABLE.put(61, 6271); DATUM_TABLE.put(62, 6267); DATUM_TABLE.put(63, 6267); DATUM_TABLE.put(64, 6267); DATUM_TABLE.put(65, 6267); DATUM_TABLE.put(66, 6609); DATUM_TABLE.put(67, 6608); DATUM_TABLE.put(68, 6267); DATUM_TABLE.put(69, 6267); DATUM_TABLE.put(70, 6267); DATUM_TABLE.put(71, 6267); DATUM_TABLE.put(72, 6267); DATUM_TABLE.put(73, 6268); DATUM_TABLE.put(74, 6140); DATUM_TABLE.put(75, 6129); DATUM_TABLE.put(76, 6229); DATUM_TABLE.put(77, 6135); DATUM_TABLE.put(78, 6134); DATUM_TABLE.put(79, 6277); DATUM_TABLE.put(80, 6728); DATUM_TABLE.put(81, 6729); DATUM_TABLE.put(82, 6248); DATUM_TABLE.put(83, 6139); DATUM_TABLE.put(83, 6139); DATUM_TABLE.put(84, 6614); DATUM_TABLE.put(85, 6194); DATUM_TABLE.put(86, 6626); DATUM_TABLE.put(86, 6626); DATUM_TABLE.put(87, 6806); DATUM_TABLE.put(88, 6663); DATUM_TABLE.put(89, 6664); DATUM_TABLE.put(90, 6292); DATUM_TABLE.put(91, 6293); DATUM_TABLE.put(92, 1075); // 93 DATUM_TABLE.put(94, 6664); DATUM_TABLE.put(95, 6665); DATUM_TABLE.put(96, 6298); DATUM_TABLE.put(97, 6301); DATUM_TABLE.put(98, 6734); DATUM_TABLE.put(99, 6752); DATUM_TABLE.put(100, 6732); // 101 DATUM_TABLE.put(102, 6760); DATUM_TABLE.put(103, 6322); DATUM_TABLE.put(104, 6326); DATUM_TABLE.put(105, 6309); DATUM_TABLE.put(106, 6311); DATUM_TABLE.put(107, 6275); DATUM_TABLE.put(108, 6231); DATUM_TABLE.put(110, 6313); DATUM_TABLE.put(111, 6043); DATUM_TABLE.put(112, 6124); // 113 DATUM_TABLE.put(114, 6274); // 115 ? DATUM_TABLE.put(116, 6283); DATUM_TABLE.put(117, 6167); DATUM_TABLE.put(118, 6169); // 119 DATUM_TABLE.put(120, 6713); DATUM_TABLE.put(121, 6219); DATUM_TABLE.put(122, 6180); DATUM_TABLE.put(123, 6155); DATUM_TABLE.put(124, 6736); // 125 DATUM_TABLE.put(126, 6183); DATUM_TABLE.put(127, 6255); // 128 // 129 DATUM_TABLE.put(130, 6239); DATUM_TABLE.put(131, 6131); DATUM_TABLE.put(132, 6240); DATUM_TABLE.put(133, 6238); // 134 DATUM_TABLE.put(135, 6735); DATUM_TABLE.put(136, 6250); DATUM_TABLE.put(137, 6604); // 138 DATUM_TABLE.put(139, 6307); DATUM_TABLE.put(140, 6182); DATUM_TABLE.put(141, 6620); DATUM_TABLE.put(142, 6282); DATUM_TABLE.put(143, 6615); DATUM_TABLE.put(144, 6616); DATUM_TABLE.put(145, 6175); DATUM_TABLE.put(146, 6818); DATUM_TABLE.put(147, 6297); DATUM_TABLE.put(148, 6671); // 149 DATUM_TABLE.put(150, 6148); DATUM_TABLE.put(151, 6122); DATUM_TABLE.put(152, 6612); DATUM_TABLE.put(157, 6326); //3 differents epsg code can be found for mapinfo code 1000 --> 6746, 6745, 6314 DATUM_TABLE.put(1000, 6314); DATUM_TABLE.put(1001, 6284); DATUM_TABLE.put(1002, 6807); DATUM_TABLE.put(1003, 4149); DATUM_TABLE.put(1004, 6237); DATUM_TABLE.put(1005, 6222); // 1006, 1007, 1008 & 1009 are australian geodetic datums (6202 & 6203) modified. How should we manage it ? DATUM_TABLE.put(1010, 6272); DATUM_TABLE.put(1011, 6124); // 1012 DATUM_TABLE.put(1013, 6178); DATUM_TABLE.put(1014, 6200); DATUM_TABLE.put(1015, 6301); DATUM_TABLE.put(1016, 6123); DATUM_TABLE.put(1017, 6610); DATUM_TABLE.put(1018, 6284); DATUM_TABLE.put(1019, 6313); DATUM_TABLE.put(1020, 6818); DATUM_TABLE.put(9999, 0); /*********************************** No EPSG equivalent datums ***********************************/ HANDED_DATUM_TABLE.put(26, buildValue("Dos_1968", 4, 230, -199, -752,0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(35, buildValue("Gux_1_Astro", 4, 252, -209, -751,0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(41, buildValue("Indian_Bangladesh", 11, 289, 734, 257, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(51, buildValue("Luzon_Mindanao_Island", 7, -133, -79, -72, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(58, buildValue("Nahrwan_Masirah_Island", 6, -247, -148, 369, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(59, buildValue("Nahrwan_Un_Arab_Emirates", 6, -249, -156, 381, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(93, buildValue("South_Asia", 19,7, -10, -26, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(101, buildValue("WGS_60", 26,0, 0, 0, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(113, buildValue("Lisboa_DLX", 4, -303, -62, 105, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(115, buildValue("Euref_98", 0, 0, 0, 0, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(119, buildValue("Antigua_Astro_1965", 6, -270, 13, 62, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(125, buildValue("Fort_Thomas_1955", 6, -7, 215, 225, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(128, buildValue("Hermanns_Kogel", 10,682, -203, 480, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(129, buildValue("Indian", 50,283, 682, 231, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(134, buildValue("ISTS061_Astro_1968", 4, -794, 119, -298, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(138, buildValue("Mporaloko", 6, -74, -130, 42, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(149, buildValue("Virol_1960", 6, -123, -206,219, 0, 0, 0, 0, 0)); HANDED_DATUM_TABLE.put(1006, buildValue("AGD84_7_Param_Aust", 2, -117.763,-51.51, 139.061, -0.292, -0.443, -0.277, -0.191, 0)); HANDED_DATUM_TABLE.put(1007, buildValue("AGD66_7_Param_ACT", 2, -129.193,-41.212, 130.73, -0.246, -0.374, -0.329, -2.955, 0)); HANDED_DATUM_TABLE.put(1008, buildValue("AGD66_7_Param_TAS", 2, -120.271,-64.543, 161.632, -0.2175, 0.0672, 0.1291, 2.4985, 0)); HANDED_DATUM_TABLE.put(1009, buildValue("AGD66_7_Param_VIC_NSW", 2, -119.353,-48.301, 139.484, -0.415, -0.26, -0.437, -0.613, 0)); HANDED_DATUM_TABLE.put(1012, buildValue("Russia_PZ90", 52, -1.08,-0.27,-0.9,0, 0, -0.16,-0.12, 0)); } }