예제 #1
0
    /**
     * @brief Build and print latlon <-> pixel transformation matrix.
     */
    private static void BuildXformMatrix ()
    {
        if (verbose) {
            Console.WriteLine ("assuming " + (landscape ? "landscape" : "portrait") + " orientation");
        }

        /*
         * Tell each cluster what the decided orientation is, in case they are confused
         * (ie, at the intersection of a vertical and horizontal line).
         */
        foreach (Cluster cluster in clusters) {
            cluster.SetOrientation (landscape);
        }

        /*
         * Find the lowest and highest lon's and lat's.
         */
        Cluster highestLat = null;
        Cluster highestLon = null;
        Cluster lowestLat  = null;
        Cluster lowestLon  = null;
        foreach (Cluster cluster in clusters) {
            if ((cluster.CenterX == 0) && (cluster.CenterY == 0)) continue;
            double lat = cluster.Latitude;
            double lon = cluster.Longitude;
            if (lat != 0) {
                if ((highestLat == null) || (highestLat.Latitude < lat)) highestLat = cluster;
                if ((lowestLat  == null) || (lowestLat.Latitude  > lat)) lowestLat  = cluster;
            }
            if (lon != 0) {
                if ((highestLon == null) || (highestLon.Longitude < lon)) highestLon = cluster;
                if ((lowestLon  == null) || (lowestLon.Longitude  > lon)) lowestLon  = cluster;
            }
        }

        if (verbose) {
            Console.WriteLine ("raw  lowestLat = " + (lowestLat  == null ? "null" : lowestLat.Latitude.ToString ()));
            Console.WriteLine ("raw highestLat = " + (highestLat == null ? "null" : highestLat.Latitude.ToString ()));
            Console.WriteLine ("raw  lowestLon = " + (lowestLon  == null ? "null" : lowestLon.Longitude.ToString ()));
            Console.WriteLine ("raw highestLon = " + (highestLon == null ? "null" : highestLon.Longitude.ToString ()));
        }

        /*
         * If fewer than two of each complain.
         * But some charts only have one of one of them (two of the other),
         * so we can fake them by assuming 1:1 pixel ratio.
         */
        string oneLinerValue = null;
        if ((lowestLat == highestLat) || (lowestLon == highestLon)) {
            if ((lowestLat == null) || (lowestLon == null) ||
                ((lowestLat == highestLat) && (lowestLon == highestLon)) ||
                !oneLiners.TryGetValue (lowestLat.Result + "," + lowestLon.Result, out oneLinerValue)) {
                throw new Exception ("fewer than two lats or lons found");
            }
        }

        /*
         * Just one latitude given, make second one by using the given two longitude lines.
         */
        if (highestLat == lowestLat) {
            Cluster madeUpLat = new Cluster (oneLinerValue);
            double f = madeUpLat.Latitude - lowestLat.Latitude;
            f /= highestLon.Longitude - lowestLon.Longitude;
            f /= Math.Cos ((lowestLat.Latitude + madeUpLat.Latitude) / 360.0 * Math.PI);
            int madeUpLatPixX = (int)((highestLon.CenterY - lowestLon.CenterY) * f + 0.5) + lowestLat.CenterX;
            int madeUpLatPixY = (int)((highestLon.CenterX - lowestLon.CenterX) * f + 0.5) + lowestLat.CenterY;
            madeUpLat.SetCenter (madeUpLatPixX, madeUpLatPixY);
            clusters.AddLast (madeUpLat);
            if (madeUpLat.Latitude > lowestLat.Latitude) {
                highestLat = madeUpLat;
            } else {
                lowestLat = madeUpLat;
            }
        }

        /*
         * Just one longitude given, make second one by using the given two latitude lines.
         */
        if (highestLon == lowestLon) {
            Cluster madeUpLon = new Cluster (oneLinerValue);
            double f = madeUpLon.Longitude - lowestLon.Longitude;
            f /= highestLat.Latitude - lowestLat.Latitude;
            f *= Math.Cos ((lowestLat.Latitude + highestLat.Latitude) / 360.0 * Math.PI);
            int madeUpLonPixX = (int)((lowestLat.CenterY - highestLat.CenterY) * f + 0.5) + lowestLon.CenterX;
            int madeUpLonPixY = (int)((lowestLat.CenterX - highestLat.CenterX) * f + 0.5) + lowestLon.CenterY;
            madeUpLon.SetCenter (madeUpLonPixX, madeUpLonPixY);
            clusters.AddLast (madeUpLon);
            if (madeUpLon.Longitude > lowestLon.Longitude) {
                highestLon = madeUpLon;
            } else {
                lowestLon = madeUpLon;
            }
        }

        /*
         * Extract values.
         */
        double lowestLatLat  = lowestLat.Latitude;
        double highestLatLat = highestLat.Latitude;
        double lowestLonLon  = lowestLon.Longitude;
        double highestLonLon = highestLon.Longitude;

        int lowestLatPixelX  = lowestLat.CenterX;
        int highestLatPixelX = highestLat.CenterX;
        int lowestLonPixelX  = lowestLon.CenterX;
        int highestLonPixelX = highestLon.CenterX;

        int lowestLatPixelY  = lowestLat.CenterY;
        int highestLatPixelY = highestLat.CenterY;
        int lowestLonPixelY  = lowestLon.CenterY;
        int highestLonPixelY = highestLon.CenterY;

        if (verbose) {
            Console.WriteLine (" lowestLat = " + lowestLat.Result  + " = " + lowestLatLat  + " at rotated (" + lowestLatPixelX  + "," + lowestLatPixelY  + ")");
            Console.WriteLine ("highestLat = " + highestLat.Result + " = " + highestLatLat + " at rotated (" + highestLatPixelX + "," + highestLatPixelY + ")");
            Console.WriteLine (" lowestLon = " + lowestLon.Result  + " = " + lowestLonLon  + " at rotated (" + lowestLonPixelX  + "," + lowestLonPixelY  + ")");
            Console.WriteLine ("highestLon = " + highestLon.Result + " = " + highestLonLon + " at rotated (" + highestLonPixelX + "," + highestLonPixelY + ")");
        }

        /*
         * For the rotated image:
         *   lon = tfwA * pixx + tfwC * pixy + tfwE
         *   lat = tfwB * pixx + tfwD * pixy + tfwF
         *
         *   pixx = (tfwD * lon - tfwC * lat - tfwD * tfwE + tfwC * tfwF) / (tfwD * tfwA - tfwC * tfwB)
         *   pixy = (tfwB * lon - tfwA * lat - tfwB * tfwE + tfwA * tfwF) / (tfwB * tfwC - tfwA * tfwD)
         */
        double tfwA = 0, tfwB = 0, tfwC = 0, tfwD = 0, tfwE = 0, tfwF = 0;

        if (highestLonPixelX != lowestLonPixelX) {
            tfwA = (highestLonLon - lowestLonLon) / (highestLonPixelX - lowestLonPixelX);
            tfwE = highestLonLon - tfwA * highestLonPixelX;
        }
        if (highestLonPixelY != lowestLonPixelY) {
            tfwC = (highestLonLon - lowestLonLon) / (highestLonPixelY - lowestLonPixelY);
            tfwE = highestLonLon - tfwC * highestLonPixelY;
        }

        if (highestLatPixelX != lowestLatPixelX) {
            tfwB = (highestLatLat - lowestLatLat) / (highestLatPixelX - lowestLatPixelX);
            tfwF = highestLatLat - tfwB * highestLatPixelX;
        }
        if (highestLatPixelY != lowestLatPixelY) {
            tfwD = (highestLatLat - lowestLatLat) / (highestLatPixelY - lowestLatPixelY);
            tfwF = highestLatLat - tfwD * highestLatPixelY;
        }

        if (verbose) {
            Console.WriteLine ("rotated tfwA=" + tfwA);
            Console.WriteLine ("rotated tfwB=" + tfwB);
            Console.WriteLine ("rotated tfwC=" + tfwC);
            Console.WriteLine ("rotated tfwD=" + tfwD);
            Console.WriteLine ("rotated tfwE=" + tfwE);
            Console.WriteLine ("rotated tfwF=" + tfwF);
        }

        /*
         * Validate by checking that all decoded lat/lon markers can be converted to their pixel number.
         */
        LinkedList<Cluster> latClusters = new LinkedList<Cluster> ();
        LinkedList<Cluster> lonClusters = new LinkedList<Cluster> ();
        foreach (Cluster cluster in clusters) {
            // verified earlier that exactly one of lat/lon is non-zero
            if (cluster.Latitude  != 0) latClusters.AddLast (cluster);
            if (cluster.Longitude != 0) lonClusters.AddLast (cluster);
        }

        bool bad = false;
        foreach (Cluster lonClus in lonClusters) {
            foreach (Cluster latClus in latClusters) {
                double lat = latClus.Latitude;
                double lon = lonClus.Longitude;
                int pixx = latClus.CenterX | lonClus.CenterX;
                int pixy = latClus.CenterY | lonClus.CenterY;
                if (verbose) Console.WriteLine ("verifying latlon " + latClus.Result + "," + lonClus.Result + " = " +
                                   lat + "," + lon + "  =>  rotated pixel " + pixx + "," + pixy);

                int comx = (int)((tfwD * lon - tfwC * lat - tfwD * tfwE + tfwC * tfwF) / (tfwD * tfwA - tfwC * tfwB) + 0.5);
                int comy = (int)((tfwB * lon - tfwA * lat - tfwB * tfwE + tfwA * tfwF) / (tfwB * tfwC - tfwA * tfwD) + 0.5);
                if (pixx == 0) pixx = comx;
                if (pixy == 0) pixy = comy;
                double clat = tfwB * pixx + tfwD * pixy + tfwF;
                double clon = tfwA * pixx + tfwC * pixy + tfwE;
                if (verbose) Console.WriteLine ("  computed rotated pixel " + comx + "," + comy + "  =>  latlon " + clat + "," + clon);

                comx -= pixx;
                comy -= pixy;
                clat -= lat;
                clon -= lon;

                if (comx < -VFYPIXERR || comx > VFYPIXERR) {
                    Console.WriteLine ("horizontal verify error " + comx);
                    bad = true;
                }
                if (comy < -VFYPIXERR || comy > VFYPIXERR) {
                    Console.WriteLine ("vertical verify error " + comy);
                    bad = true;
                }
                if (clat < -VFYDEGERR || clat > VFYDEGERR) {
                    Console.WriteLine ("latitude verify error " + clat);
                    bad = true;
                }
                if (clon < -VFYDEGERR || clon > VFYDEGERR) {
                    Console.WriteLine ("longitude verify error " + clon);
                    bad = true;
                }
            }
        }

        /*
         * Also, lat and lon pixel sizes should be the same.
         */
        double lonperpix = Math.Abs (tfwA + tfwC);  // only one of tfwA,tfwC is non-zero
        double latperpix = Math.Abs (tfwB + tfwD);  // only one of tfwB,tfwD is non-zero
        double latcosine = Math.Cos ((highestLatLat + lowestLatLat) / 360.0 * Math.PI);
        double llperpixratio = lonperpix * latcosine / latperpix;
        double ratioshouldbe;
        if (!notsquare.TryGetValue (csvoutid, out ratioshouldbe)) {
            ratioshouldbe = 1.0;
        }
        if (verbose) {
            Console.WriteLine ("lon per pix=" + lonperpix + ", lat per pix=" + latperpix + ", ratio=" + llperpixratio + ", expecting=" + ratioshouldbe);
        }
        llperpixratio /= ratioshouldbe;
        if (llperpixratio > 1.0) llperpixratio = 1.0 / llperpixratio;
        if (llperpixratio < VFYPIXRAT) {
            Console.WriteLine ("pixels are not square");
            bad = true;
        }

        if (bad) throw new Exception ("latlon <-> rotated pixel verify error");

        /*
         * Now we have:
         *
         *    Mr * TCAr * Rc * TACo * Po = L
         *
         *      Mr = [ tfwA tfwC tfwE ]     the TFW matrix from above calcuations
         *           [ tfwB tfwD tfwF ]     that will transform a rotated pixel
         *           [   0    0    1  ]     into lat/lon
         *
         *      TCAr = [ 1 0  Wr/2 ]        translate center-relative to absolue in the rotated image
         *             [ 0 1  Hr/2 ]
         *             [ 0 0   1   ]
         *
         *      Rc = [  cos th  sin th  0 ] rotates a center-relative pixel in the original image
         *           [ -sin th  cos th  0 ] to corresponding center-relative pixel in rotated image
         *           [    0       0     1 ]
         *
         *      TACo = [ 1 0 -Wo/2 ]        translate absolute to center-relative in the original image
         *             [ 0 1 -Ho/2 ]
         *             [ 0 0   1   ]
         *
         *      Po = [ Xo ]                 an arbitrary pixel in the original image
         *           [ Yo ]
         *           [  1 ]
         *
         *      L  = [ lon ]                the corresponding lat/lon coordinate in the real world
         *           [ lat ]
         *           [  1  ]
         */
        double[,] TACo = new double[3,3] { { 1, 0, -origWidth  / 2.0 },
                                           { 0, 1, -origHeight / 2.0 },
                                           { 0, 0,             1     } };

        double denom = Math.Sqrt (latLonSlopeX * latLonSlopeX + latLonSlopeY * latLonSlopeY);
        double costh = latLonSlopeX / denom;
        double sinth = latLonSlopeY / denom;
        double[,] Rc   = new double[3,3] { {  costh, sinth, 0 },
                                           { -sinth, costh, 0 },
                                           {    0,     0,   1 } };

        double[,] TCAr = new double[3,3] { { 1, 0, portWidth  / 2.0 },
                                           { 0, 1, portHeight / 2.0 },
                                           { 0, 0,            1     } };

        double[,] Mr   = new double[3,3] { { tfwA, tfwC, tfwE },
                                           { tfwB, tfwD, tfwF },
                                           {   0,    0,    1  } };

        /*
         * And so we can compute:
         *
         *    Mo = Mr * TCAr * Rc * TACo
         *
         * Such that:
         *
         *    Mo * Po = L
         */
        double[,] Mo = MatMul (MatMul (Mr, TCAr), MatMul (Rc, TACo));

        /*
         * Extract TFW values that convert a pixel in the original image
         * directly to the corresponding latitude/longitude.
         */
        tfwA = Mo[0,0];
        tfwB = Mo[1,0];
        tfwC = Mo[0,1];
        tfwD = Mo[1,1];
        tfwE = Mo[0,2];
        tfwF = Mo[1,2];

        if (verbose) {
            Console.WriteLine ("final tfwA=" + tfwA);
            Console.WriteLine ("final tfwB=" + tfwB);
            Console.WriteLine ("final tfwC=" + tfwC);
            Console.WriteLine ("final tfwD=" + tfwD);
            Console.WriteLine ("final tfwE=" + tfwE);
            Console.WriteLine ("final tfwF=" + tfwF);
        }

        /*
         * Calculate inverse TFW values to convert a lat/lon to a pixel in
         * the original image.
         *
         * Compute:
         *    MoInv = 1 / Mo
         *
         * Given from above:
         *    Mo * Po = L
         *
         * Multiply both sides by MoInv:
         *    MoInv * Mo * Po = MoInv * L
         *
         * And we get:
         *    Po = MoInv * L
         *
         *    pixx = wftA * lon + wftC * lat + wftE
         *    pixy = wftB * lon + wftD * lat + wftF
         */
        double[,] MoInv = MatInv (Mo);
        double wftA = MoInv[0,0];
        double wftB = MoInv[1,0];
        double wftC = MoInv[0,1];
        double wftD = MoInv[1,1];
        double wftE = MoInv[0,2];
        double wftF = MoInv[1,2];

        if (verbose) {
            Console.WriteLine ("final wftA=" + wftA);
            Console.WriteLine ("final wftB=" + wftB);
            Console.WriteLine ("final wftC=" + wftC);
            Console.WriteLine ("final wftD=" + wftD);
            Console.WriteLine ("final wftE=" + wftE);
            Console.WriteLine ("final wftF=" + wftF);
        }

        /*
         * Maybe write to .csv file.
         * Lines are of the form:
         *   csvoutid,tfwA,tfwB,tfwC,tfwD,tfwE,tfwF,wftA,wftB,wftC,wftD,wftE,wftF
         */
        if ((csvoutfile != null) && (csvoutid != null)) {
            StringBuilder sb = new StringBuilder ();
            sb.Append (csvoutid);
            sb.Append (',');
            sb.Append (tfwA);
            sb.Append (',');
            sb.Append (tfwB);
            sb.Append (',');
            sb.Append (tfwC);
            sb.Append (',');
            sb.Append (tfwD);
            sb.Append (',');
            sb.Append (tfwE);
            sb.Append (',');
            sb.Append (tfwF);
            sb.Append (',');
            sb.Append (wftA);
            sb.Append (',');
            sb.Append (wftB);
            sb.Append (',');
            sb.Append (wftC);
            sb.Append (',');
            sb.Append (wftD);
            sb.Append (',');
            sb.Append (wftE);
            sb.Append (',');
            sb.Append (wftF);
            sb.Append ('\n');
            File.WriteAllText (csvoutfile, sb.ToString ());
        }

        /*
         * Maybe create marked-up .png file.
         */
        if (markedpng != null) {

            /*
             * Get original image with white background.
             */
            bmp    = new Bitmap (pngname);
            width  = bmp.Width;
            height = bmp.Height;
            for (int y = 0; y < height; y ++) {
                for (int x = 0; x < width; x ++) {
                    Color c = bmp.GetPixel (x, y);
                    int w = 255 - c.A;
                    if (w != 0) {
                        bmp.SetPixel (x, y, Color.FromArgb (c.R + w, c.G + w, c.B + w));
                    }
                }
            }

            /*
             * Draw red crosses wherever lat/lon lines intersect.
             */
            foreach (Cluster lonClus in lonClusters) {
                foreach (Cluster latClus in latClusters) {
                    double lat = latClus.Latitude;
                    double lon = lonClus.Longitude;
                    int x = (int)(wftA * lon + wftC * lat + wftE + 0.5);
                    int y = (int)(wftB * lon + wftD * lat + wftF + 0.5);
                    DrawCross (x, y, 25, 2, Color.Red);
                }
            }

            bmp.Save (markedpng);
        }
    }