Package org.locationtech.udig.mapgraphic.graticule

Source Code of org.locationtech.udig.mapgraphic.graticule.GraticuleGraphic$Line

/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2012, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*/
package org.locationtech.udig.mapgraphic.graticule;

import java.awt.Color;
import java.awt.Font;
import java.awt.Point;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;

import javax.measure.unit.SI;
import javax.measure.unit.Unit;

import org.locationtech.udig.mapgraphic.MapGraphic;
import org.locationtech.udig.mapgraphic.MapGraphicContext;
import org.locationtech.udig.mapgraphic.MapGraphicPlugin;
import org.locationtech.udig.mapgraphic.internal.Messages;
import org.locationtech.udig.project.ILayer;
import org.locationtech.udig.project.internal.Layer;
import org.locationtech.udig.ui.graphics.ViewportGraphics;

import org.eclipse.swt.graphics.Path;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PlatformUI;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.grid.Grids;
import org.geotools.referencing.CRS;
import org.geotools.resources.CRSUtilities;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;

/**
* <b>Graticule Graphic class</b>
* <p>
* Draws a geographic coordinate grid on given {@link ViewportGraphics} (also known as a
* 'graticule')
* <p>
* <b>NOTE</b>: Only CRS with unit {@link SI#METER} is supported.
* </p>
*/
public class GraticuleGraphic implements MapGraphic {

    private static final String DELIM = "-"; //$NON-NLS-1$

    private static final String EMPTY = ""; //$NON-NLS-1$

    private static final String GEOM = "element"; //$NON-NLS-1$

    private static final String FORMAT = "0000000"; //$NON-NLS-1$

    private static final int MAX_SCALE = 100000000;

    SimpleFeatureSource grid;

    NumberFormat cf = new DecimalFormat(FORMAT);

    @Override
    public void draw(MapGraphicContext context) {

        // Initialize
        ILayer graticule = context.getLayer();
        GraticuleStyle style = GraticuleStyle.getStyle(graticule);

        // Ensure CRS?
        if (graticule instanceof Layer) {
            // Initialize CRS?
            if (style.isInitCRS()) {
                // Only initialize once
                style.setInitCRS(false);
                // Apply CRS from context
                GraticuleCRSConfigurator.apply((Layer) graticule, context.getCRS());
            } else if (mismatch(graticule, style)) {
                // Apply CRS from
                GraticuleCRSConfigurator.apply((Layer) graticule, style.getCRS());
            }
        }

        // Sanity checks
        if (MAX_SCALE < context.getMap().getViewportModel().getScaleDenominator()) {
            graticule.setStatus(ILayer.WARNING);
            graticule.setStatusMessage(Messages.GraticuleGraphic_Maximum_Scale + MAX_SCALE);
            return;
        }
        Unit<?> unit = CRSUtilities.getUnit(graticule.getCRS().getCoordinateSystem());
        if (!(unit==null || SI.METER.equals(unit))) {
//        if (!(unit==null || SI.METER.equals(unit) || SI.RADIAN.equals(unit.getStandardUnit()))) { // $NON-NLS-1$
            graticule.setStatus(ILayer.ERROR);
            graticule.setStatusMessage(Messages.GraticuleGraphic_Illegal_CRS);
            return;
        }
        final IWorkbench workbench = PlatformUI.getWorkbench();
        if (workbench == null)
            return;

        // Start working on layer
        graticule.setStatus(ILayer.WORKING);
        graticule.setStatusMessage(null);

        // Get display to work on
        final Display display = workbench.getDisplay();

        // Set styles
        Font plain = GraticuleStyle.getFontStyle(context).getFont();
        Font bold = plain.deriveFont(Font.BOLD);

        // Initialize the graphics handle
        ViewportGraphics g = context.getGraphics();

        // Set font size
        g.setFont(bold);
       
        // Show labels?
        boolean isShowLabels = style.isShowLabels();

        // Get bounds of viewport
        ReferencedEnvelope bounds = context.getViewportModel().getBounds();

        try {

            // Get square size limited to minimum size of 100 pixels
            double size = size(context, unit, 10);

            // Convert square size to pixels
            int sx = (int) (size / context.getViewportModel().getPixelSize().x);
            int sy = (int) (size / context.getViewportModel().getPixelSize().y);

//          // Sanity check
            if (sx < 10 || sy < 10) {
                graticule.setStatus(ILayer.ERROR);
                graticule.setStatusMessage(Messages.GraticuleGraphic_Error_Too_Many_Squares);
                return;
            }

            // Make transform from Graticule to map CRS
            MathTransform transform = CRS.findMathTransform(graticule.getCRS(), context.getCRS(),
                    false);

            // Transform bounds into Graticule CRS
            bounds = bounds.transform(graticule.getCRS(), true);

            // Get squares inside bounds
            SimpleFeatureIterator it = squares(bounds, size);

//            // Initialize border tick positions
//            List<Geometry> ticks = new ArrayList<Geometry>();
//
//            // Get map borders as geometry in graticule CRS
//            Geometry outer = JTS.toGeometry(bounds);
//            Geometry inner = JTS.toGeometry(expand(bounds,-500));

            // Draw one squares at the time (only top and left lines are drawn)
            while (it.hasNext()) {

                // Initialize states
                int i = 0;
                Point current = null;

                // Initialize lines
                List<Line> lines = new ArrayList<Line>(2);
                List<Label> labels = new ArrayList<Label>(2);

                // Get next geometry
                Geometry geom = (Geometry) it.next().getDefaultGeometry();

                // Get coordinates in graticule CRS
                Coordinate[] coords = geom.getCoordinates();

                // Get x-coordinate label from upper left corner
                String tx = getLabel(coords[0].x, size, unit, style);

                // Get y-coordinate label from lower left corner
                String ty = getLabel(coords[2].y, size, unit, style);

                // Insert gap with label?
                boolean vgap = isGap(tx, unit, style);
                boolean hgap = isGap(ty, unit, style);

                // Transform coordinates into Map CRS
                coords = JTS.transform(geom, transform).getCoordinates();

                // Create lines and labels for this square
                for (Coordinate c : coords) {

                    // Build paths
                    switch (i) {
                    case 1:

                        // -----------------------
                        // Vertical line
                        // -----------------------

                        // Create line path
                        current = vert(display, g, sy, style.getLineWidth(), current,
                                context.worldToPixel(c), isShowLabels & hgap, vgap, lines);

                        // Add xx label?
                        if (isShowLabels & hgap) {
                            labels.add(new Label(current, tx, vgap ? bold : plain));
                            current = context.worldToPixel(c);
                        }

                        break;
                    case 2:

                        // -----------------------
                        // Horizontal line
                        // -----------------------

                        // Create line path
                        current = horz(display, g, sx, style.getLineWidth(), current,
                                context.worldToPixel(c), isShowLabels & vgap, hgap, lines);

                        // Add yy label?
                        if (isShowLabels & vgap) {
                            labels.add(new Label(current, ty, hgap ? bold : plain));
                            current = context.worldToPixel(c);
                        }

                        break;

                    default:
                        current = context.worldToPixel(c);
                        break;

                    }
                    i++;
                }

//                // Is square on border?
//                if (geom.crosses(outer) || geom.overlaps(outer)) {
//                    // Get intersection as multi-point geometry
//                    ticks.add(geom.intersection(outer));
//                }

                // Draw lines
                for (Line line : lines)
                    line.draw(g, style);

                // Draw labels?
                if (style.isShowLabels())
                    for (Label label : labels)
                        label.draw(g, style);

            }

//            // Draw borders?
//            if (false && !ticks.isEmpty()) {
//
////                System.out.println("Ticks: " + ticks.size() + ", " + ticks);
////                outer = JTS.transform(outer, context.worldToScreenMathTransform());
//               
//                // Print borders
//                g.setColor(Color.RED);
//                g.setStroke(ViewportGraphics.LINE_SOLID, 2);
//                for (Geometry tick : ticks) {
//                    int i = 0;
//                    Path path = new Path(display);
////                    tick = tick.intersection(inner);
//                    if(tick.isEmpty()) continue;
//                    // Transform coordinates to map CRS
//                    tick = JTS.transform(tick, transform);
//                   
//                    int b = 10;
//                    int w = context.getMapDisplay().getWidth();
//                    int h = context.getMapDisplay().getHeight();
//                   
////                    tick = tick.getEnvelope();
//                    for(Coordinate c : tick.getCoordinates()) {
//                        Point p = context.worldToPixel(c);
////                        if(p.x<b) p.x=b;
////                        if(p.y<b) p.y=b;
////                        if(p.x>w-b) p.x=w-b;
////                        if(p.y>h-b) p.y=h-b;
//                        if(i==0) path.moveTo(p.x, p.y);
//                        else path.lineTo(p.x, p.y);
//                        i++;
//                    }
//                    // Calculate intersection with inner border
//                   
////                    Point p = context.worldToPixel(null);
////                    g.drawOval(p.x, p.y, 10, 10);                       
//                    g.drawPath(path);
//                }
//            }

            // // Get lower left corner coordinates
            // int x = llc.x;
            // int y = llc.y;
            //
            // // Print borders
            // g.setColor(lc);
            // g.setStroke(ViewportGraphics.LINE_SOLID, 5);
            //
            // // Inner rectangle
            // g.drawRect(x + d + l, y + d + l, w - 2 * (d + l), h - 2 * (d + l));
            //
            // // Make white border
            // g.setColor(Color.WHITE);
            //
            // // Left
            // g.drawRect(x, y, d, h);
            // g.fillRect(x, y, d, h);
            //
            // // Bottom
            // g.drawRect(x, y, w, d);
            // g.fillRect(x, y, w, d);
            //
            // // Right
            // g.drawRect(x + w - d, y, d, h);
            // g.fillRect(x + w - d, y, d, h);
            //
            // // Top
            // g.drawRect(x, y + h - d, w, d);
            // g.fillRect(x, y + h - d, w, d);

        } catch (IOException ex) {
            MapGraphicPlugin.log(Messages.GraticuleGraphic_Error, ex);
        } catch (FactoryException ex) {
            MapGraphicPlugin.log(Messages.GraticuleGraphic_Error, ex);
        } catch (MismatchedDimensionException ex) {
            MapGraphicPlugin.log(Messages.GraticuleGraphic_Error, ex);
        } catch (TransformException ex) {
            MapGraphicPlugin.log(Messages.GraticuleGraphic_Error, ex);
        }

        // Finished working on layer
        graticule.setStatus(ILayer.DONE);

    }

    private boolean mismatch(ILayer graticule, GraticuleStyle style) {
        String code;
        try {
            code = "EPSG:" + CRS.lookupEpsgCode(graticule.getCRS(), false); //$NON-NLS-1$
            return !code.equals(style.getCRS());
        } catch (FactoryException ex) {
            MapGraphicPlugin.log(Messages.GraticuleGraphic_Error, ex);
        }
        return true;
    }

    /**
     * Generate squares inside given bounds
     *
     * @param bounds
     * @param size
     * @return
     * @throws IOException
     */
    private SimpleFeatureIterator squares(ReferencedEnvelope bounds, double size)
            throws IOException {

        // Limit squares to bounding box
        FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);
        Filter filter = ff.bbox(ff.property(GEOM), bounds);

        // Align bound to square size and expand by 150%
        bounds = align(bounds, size, 1.6);

        // Create grid for given bounds
        SimpleFeatureSource grid = Grids.createSquareGrid(bounds, size);

        // Finished
        return grid.getFeatures(filter).features();
    }

    /**
     * Expand bounds by given fraction
     *
     *
     * @param bounds
     * @param distance
     * @return ReferencedEnvelope
     */
    private ReferencedEnvelope expand(ReferencedEnvelope bounds, double distance) {
        bounds = new ReferencedEnvelope(bounds);
        bounds.expandBy(distance);
        return bounds;
    }
   
    /**
     * Align given bounds with square size and expand by given fraction
     *
     *
     * @param bounds
     * @param size
     * @return
     */
    private ReferencedEnvelope align(ReferencedEnvelope bounds, double size, double expand) {
        bounds = expand(bounds, expand * size);
        double x = offset(bounds.getLowerCorner(), 0, size);
        double y = offset(bounds.getLowerCorner(), 1, size);
        bounds.translate(-x, -y);
        return bounds;
    }

    /**
     * Get ordinate offset from given size.
     *
     * @param point
     * @param ordinate
     * @param size
     * @return
     */
    private double offset(DirectPosition point, int ordinate, double size) {
        return point.getOrdinate(ordinate) % size;
    }

    /**
     * Calculate appropriate square size in CRS units.
     *
     * @param context - {@link MapGraphicContext} instance
     * @param unit - CRS {@link Unit}
     * @param min - minimum pixel size
     * @return double
     */
    private double size(MapGraphicContext context, Unit<?> unit, int min) {
        // Get scale
        double scale = context.getMap().getViewportModel().getScaleDenominator();
        // 1 km square?
        if (scale < 100000)
            return 1000.0;
        // 10 km square?
        if (scale < 1000000)
            return 10000.0;
        // 100 km square?
        if (scale < 10000000)
            return 100000.0;
        // 1000 km square?
        if (scale < 100000000)
            return 1000000.0;
        // 10000 km square
        return 100000000.0;
//        // Get flags
//        boolean meter = SI.METER.equals(unit);
//        // Get scale
//        double scale = context.getMap().getViewportModel().getScaleDenominator();
//        // 1 km square?
//        if (scale < 100000)
//            return meter ? 1000.0 : unit.getConverterTo(SI.METER).convert(0.1);
//        // 10 km square?
//        if (scale < 1000000)
//            return meter ? 10000.0 : unit.getConverterTo(SI.METER).convert(1.0);
//        // 100 km square?
//        if (scale < 10000000)
//            return meter ? 100000.0 : unit.getConverterTo(SI.METER).convert(5.0);
//        // 1000 km square?
//        if (scale < 100000000)
//            return meter ? 1000000.0 : unit.getConverterTo(SI.METER).convert(10.0);
//        // 10000 km square
//        return meter ? 100000000.0 : unit.getConverterTo(SI.METER).convert(50.0);
    }

    /**
     * Check if given coordinate label is shown
     *
     * @param coordinate - coordinate label
     * @param unit - coordinate unit
     * @param style - graticule style
     * @return boolean
     */
    private boolean isGap(String coordinate, Unit<?> unit, GraticuleStyle style) {
        return (Integer.valueOf(coordinate) % 10 == 0);
    }

    /**
     * Get grid-line label
     *
     * @param coordinate - line coordinate
     * @param size - square size in coordinate units
     * @param unit - coordinate unit
     * @param style - graticule style
     * @return String
     */
    private String getLabel(double coordinate, double size, Unit<?> unit, GraticuleStyle style) {

        // Set number of digits
        int digits = 2;

        // Calculate offset
        int offset = Math.max(1, 3 - String.valueOf(Math.round(size / 1000)).length());

        // Finished
        return (Math.signum(coordinate) == -1 ? DELIM : EMPTY)
                + cf.format(Math.abs(coordinate)).substring(offset, offset + digits);
    }

    /**
     * Offset point from mid-point between p1 and p2 given length
     *
     * @see {@linkplain http
     *      ://www.teacherschoice.com.au/Maths_Library/Analytical%20Geometry/AnalGeom_3.htm}
     *
     * @param p1
     * @param p2
     * @param length
     * @param internal
     * @return Point
     */
    private Point offset(Point p1, Point p2, int offset) {

        // Get segment length
        int segment = (int) p1.distance(p2);

        // Calculate length along line from p1
        int length = segment / 2 + offset;

        // Calculate ratio
        int k1 = length;
        int k2 = segment - length;

        // Calculate division of line segment
        int x = (k1 * p2.x + k2 * p1.x) / (k1 + k2);
        int y = (k1 * p2.y + k2 * p1.y) / (k1 + k2);

        // Finished
        return new Point(x, y);
    }

    /**
     * Create vertical line
     *
     * @param display - active {@link Display}
     * @param g - active {@link ViewportGraphics}
     * @param sy - Square height
     * @param lw - line width
     * @param current - Current square coordinate (corner)
     * @param next - Next square coordinate (corner)
     * @param gap - Gap where label is inserted (pixels)
     * @param bold - flag controlling line width
     * @param lines - already created square lines
     * @return 'next' coordinate
     */
    private Point vert(Display display, ViewportGraphics g, int sy, int lw, Point current,
            Point next, boolean gap, boolean bold, List<Line> lines) {

        // Initialize
        List<Path> paths = new ArrayList<Path>(2);

        // Create first segment
        Path path = new Path(display);

        // Move to last point
        path.moveTo(current.x, current.y - (bold ? 2 * lw : lw));

        // Insert gap?
        if (gap) {

            // Calculate gap/2
            int offset = Math.max(1, g.getFontHeight());

            // End first segment before mid-point
            Point p = offset(current, next, -offset);
            path.lineTo(p.x, p.y);
            paths.add(path);

            // Create second segment
            path = new Path(display);

            // Move past mid-point
            p = offset(current, next, offset);
            path.moveTo(p.x, p.y);

        }

        // Close path
        path.lineTo(next.x, next.y + (bold ? 0 : lw));
        paths.add(path);
        lines.add(new Line(paths, bold ? 2 * lw : lw));

        // Finished
        return gap ? offset(current, next, 0) : next;
    }

    /**
     * Create horizontal line
     *
     * @param display - active {@link Display}
     * @param g - active {@link ViewportGraphics}
     * @param sx - Square width
     * @param lw - line width
     * @param current - Current square coordinate (corner)
     * @param next - Next square coordinate (corner)
     * @param gap - Gap where label is inserted (pixels)
     * @param bold - flag controlling line width
     * @param lines - already created square lines
     * @return 'next' coordinate
     */
    private Point horz(Display display, ViewportGraphics g, int sx, int lw, Point current,
            Point next, boolean gap, boolean bold, List<Line> lines) {

        // Initialize
        List<Path> paths = new ArrayList<Path>(2);

        // Create first segment
        Path path = new Path(display);

        // Move to last point
        path.moveTo(current.x, current.y);

        // Insert gap?
        if (gap) {

            // Calculate gap/2
            int offset = Math.max(1, g.getFontHeight());

            // End first segment before mid-point
            Point p = offset(current, next, -offset);
            path.lineTo(p.x, p.y);
            paths.add(path);

            // Create second segment
            path = new Path(display);

            // Move past mid-point
            p = offset(current, next, offset);
            path.moveTo(p.x, p.y);

        }

        // Close path
        path.lineTo(next.x - (bold ? 2 * lw : lw), next.y);
        paths.add(path);
        lines.add(new Line(paths, bold ? 2 * lw : lw));

        // Finished
        return gap ? offset(current, next, 0) : next;
    }

    /**
     * Line definition class
     */
    private static class Line {

        int w;

        List<Path> paths;

        public Line(List<Path> paths, int w) {
            this.w = w;
            this.paths = paths;
        }

        public void draw(ViewportGraphics g, GraticuleStyle style) {
            g.setColor(style.getLineColor());
            g.setStroke(style.getLineStyle(), w);
            for (Path path : paths)
                g.drawPath(path);
        }
    }

    /**
     * Label definition class
     */
    private static class Label {

        Font font;

        String text;

        Point anchor;

        public Label(Point anchor, String text, Font font) {
            this.font = font;
            this.text = text;
            this.anchor = anchor;
        }

        public void draw(ViewportGraphics g, GraticuleStyle style) {
            Color old = g.getColor();
            g.setColor(style.getFontColor());
            g.setFont(font);
            g.drawString(text, anchor.x, anchor.y, ViewportGraphics.ALIGN_MIDDLE,
                    ViewportGraphics.ALIGN_MIDDLE);
            // Restore old state
            g.setColor(old);
        }
    }

}
TOP

Related Classes of org.locationtech.udig.mapgraphic.graticule.GraticuleGraphic$Line

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.