public void SimpleSimplifyPointF2DCurveTests()
        {
            double epsilon = 0.1;

            // simple 2-point line should remain identical.
            PointF2D[] testCurve = new PointF2D[] {
                new PointF2D(0, 0),
                new PointF2D(1, 1)
            };
            PointF2D[] simpleTestCurve = SimplifyCurve.Simplify(testCurve, epsilon);
            Assert.AreEqual(0, simpleTestCurve [0] [0]);
            Assert.AreEqual(0, simpleTestCurve [0] [1]);
            Assert.AreEqual(1, simpleTestCurve [1] [0]);
            Assert.AreEqual(1, simpleTestCurve [1] [1]);

            // simple 1-point line should remain identical.
            testCurve = new PointF2D[] {
                new PointF2D(0, 0),
                new PointF2D(1, 1)
            };
            //PointF2D[] simpleTextCurve = SimplifyCurve.SimplifyBetween(testCurve, epsilon, 0, 0);

            // simple straight line should be simplified to only 2 points.
            testCurve = new PointF2D[] {
                new PointF2D(0, 0),
                new PointF2D(0.5, 0.5),
                new PointF2D(1, 1)
            };
            simpleTestCurve = SimplifyCurve.Simplify(testCurve, epsilon);
            Assert.AreEqual(0, simpleTestCurve [0] [0]);
            Assert.AreEqual(0, simpleTestCurve [0] [1]);
            Assert.AreEqual(1, simpleTestCurve [1] [0]);
            Assert.AreEqual(1, simpleTestCurve [1] [1]);

            // this line should retain all points.
            testCurve = new PointF2D[] {
                new PointF2D(0, 0),
                new PointF2D(1, 1),
                new PointF2D(0, 1)
            };
            simpleTestCurve = SimplifyCurve.Simplify(testCurve, epsilon);
            Assert.AreEqual(0, simpleTestCurve [0] [0]);
            Assert.AreEqual(0, simpleTestCurve [0] [1]);
            Assert.AreEqual(1, simpleTestCurve [1] [0]);
            Assert.AreEqual(1, simpleTestCurve [1] [1]);
            Assert.AreEqual(0, simpleTestCurve [2] [0]);
            Assert.AreEqual(1, simpleTestCurve [2] [1]);

            // this line should be simplified.
            testCurve = new PointF2D[] {
                new PointF2D(0, 0),
                new PointF2D(0.2, 0.21),
                new PointF2D(0.52, 0.5),
                new PointF2D(0.75, 0.76),
                new PointF2D(1, 1)
            };
            simpleTestCurve = SimplifyCurve.Simplify(testCurve, epsilon);
            Assert.AreEqual(0, simpleTestCurve [0] [0]);
            Assert.AreEqual(0, simpleTestCurve [0] [1]);
            Assert.AreEqual(1, simpleTestCurve [1] [0]);
            Assert.AreEqual(1, simpleTestCurve [1] [1]);
        }
        public void SimpleSimplifyCurveTests()
        {
            double epsilon = 0.1;

            // simple 2-point line should remain identical.
            var testCurve = new double[][] {
                new double[] { 0, 1 },
                new double[] { 0, 1 }
            };

            double[][] simpleTestCurve = SimplifyCurve.Simplify(testCurve, epsilon);
            Assert.AreEqual(0, simpleTestCurve[0][0]);
            Assert.AreEqual(0, simpleTestCurve[1][0]);
            Assert.AreEqual(1, simpleTestCurve[0][1]);
            Assert.AreEqual(1, simpleTestCurve[1][1]);

            // between with identical start-end should return one point.
            testCurve = new double[][] {
                new double[] { 0, 1 },
                new double[] { 0, 1 }
            };
            simpleTestCurve = SimplifyCurve.SimplifyBetween(testCurve, epsilon, 0, 0);
            Assert.AreEqual(0, simpleTestCurve[0][0]);
            Assert.AreEqual(0, simpleTestCurve[1][0]);

            // simple straight line should be simplified to only 2 points.
            testCurve = new double[][] {
                new double[] { 0, 0.5, 1 },
                new double[] { 0, 0.5, 1 }
            };
            simpleTestCurve = SimplifyCurve.Simplify(testCurve, epsilon);
            Assert.AreEqual(0, simpleTestCurve[0][0]);
            Assert.AreEqual(0, simpleTestCurve[1][0]);
            Assert.AreEqual(1, simpleTestCurve[0][1]);
            Assert.AreEqual(1, simpleTestCurve[1][1]);

            // this line should retain all points.
            testCurve = new double[][] {
                new double[] { 0, 1, 0 },
                new double[] { 0, 1, 1 }
            };
            simpleTestCurve = SimplifyCurve.Simplify(testCurve, epsilon);
            Assert.AreEqual(0, simpleTestCurve[0][0]);
            Assert.AreEqual(0, simpleTestCurve[1][0]);
            Assert.AreEqual(1, simpleTestCurve[0][1]);
            Assert.AreEqual(1, simpleTestCurve[1][1]);
            Assert.AreEqual(0, simpleTestCurve[0][2]);
            Assert.AreEqual(1, simpleTestCurve[1][2]);


            // this line should be simplified.
            testCurve = new double[][] {
                new double[] { 0, 0.2, 0.52, 0.75, 1 },
                new double[] { 0, 0.21, 0.5, 0.76, 1 }
            };
            simpleTestCurve = SimplifyCurve.Simplify(testCurve, epsilon);
            Assert.AreEqual(0, simpleTestCurve[0][0]);
            Assert.AreEqual(0, simpleTestCurve[1][0]);
            Assert.AreEqual(1, simpleTestCurve[0][1]);
            Assert.AreEqual(1, simpleTestCurve[1][1]);

            // this 'area' should not be simplified.
            testCurve = new double[][] {
                new double[] { 0, 1, 1, 0, 0 },
                new double[] { 0, 0, 1, 1, 0 }
            };
            simpleTestCurve = SimplifyCurve.Simplify(testCurve, epsilon);
            Assert.AreEqual(0, simpleTestCurve[0][0]);
            Assert.AreEqual(0, simpleTestCurve[1][0]);
            Assert.AreEqual(1, simpleTestCurve[0][1]);
            Assert.AreEqual(0, simpleTestCurve[1][1]);
            Assert.AreEqual(1, simpleTestCurve[0][2]);
            Assert.AreEqual(1, simpleTestCurve[1][2]);
            Assert.AreEqual(0, simpleTestCurve[0][3]);
            Assert.AreEqual(1, simpleTestCurve[1][3]);
            Assert.AreEqual(0, simpleTestCurve[0][4]);
            Assert.AreEqual(0, simpleTestCurve[1][4]);
        }
        /// <summary>
        /// Translates a way.
        /// </summary>
        /// <param name="scene">The scene to add primitives to.</param>
        /// <param name="projection">The projection used to convert the objects.</param>
        /// <param name="way"></param>
        private void TranslateWay(Scene2D scene, IProjection projection, CompleteWay way)
        {
            // build the rules.
            List <MapCSSRuleProperties> rules =
                this.BuildRules(new MapCSSObject(way));

            // validate what's there.
            if (rules.Count == 0)
            {
                return;
            }

            // get x/y.
            double[] x = null, y = null;
            if (x == null)
            { // pre-calculate x/y.
                x = new double[way.Nodes.Count];
                y = new double[way.Nodes.Count];
                for (int idx = 0; idx < way.Nodes.Count; idx++)
                {
                    x[idx] = projection.LongitudeToX(
                        way.Nodes[idx].Coordinate.Longitude);
                    y[idx] = projection.LatitudeToY(
                        way.Nodes[idx].Coordinate.Latitude);
                }

                // simplify.
                if (x.Length > 2)
                {
                    double[][] simplified = SimplifyCurve.Simplify(new double[][] { x, y }, 0.0001);
                    x = simplified[0];
                    y = simplified[1];
                }
            }

            // add the z-index.
            foreach (var rule in rules)
            {
                float minZoom = (float)projection.ToZoomFactor(rule.MinZoom);
                float maxZoom = (float)projection.ToZoomFactor(rule.MaxZoom);

                int zIndex;
                if (!rule.TryGetProperty <int>("zIndex", out zIndex))
                {
                    zIndex = 0;
                }

                // interpret the results.
                if (x != null)
                { // there is a valid interpretation of this way.
                    int  color;
                    bool renderAsLine = true;
                    if (way.IsOfType(MapCSSTypes.Area))
                    { // the way is an area. check if it can be rendered as an area.
                        int fillColor;
                        if (rule.TryGetProperty("fillColor", out fillColor))
                        { // render as an area.
                            uint?pointsId = scene.AddPoints(x, y);
                            if (pointsId.HasValue)
                            {
                                scene.AddStylePolygon(pointsId.Value, this.CalculateSceneLayer(OffsetArea, zIndex), minZoom, maxZoom, fillColor, 1, true);
                                if (rule.TryGetProperty("color", out color))
                                {
                                    scene.AddStylePolygon(pointsId.Value, this.CalculateSceneLayer(OffsetCasing, zIndex), minZoom, maxZoom, color, 1, false);
                                }
                            }
                            renderAsLine = false; // was validly rendered als a line.
                        }
                    }

                    if (renderAsLine)
                    { // was not rendered as an area.
                        // the way has to rendered as a line.
                        LineJoin lineJoin;
                        if (!rule.TryGetProperty("lineJoin", out lineJoin))
                        {
                            lineJoin = LineJoin.Miter;
                        }
                        int[] dashes;
                        if (!rule.TryGetProperty("dashes", out dashes))
                        {
                            dashes = null;
                        }
                        if (rule.TryGetProperty("color", out color))
                        {
                            float casingWidth;
                            int   casingColor;
                            if (!rule.TryGetProperty("casingWidth", out casingWidth))
                            {
                                casingWidth = 0;
                            }
                            if (!rule.TryGetProperty("casingColor", out casingColor))
                            { // casing: use the casing layer.
                                casingColor = -1;
                            }
                            float width;
                            if (!rule.TryGetProperty("width", out width))
                            {
                                width = 1;
                            }
                            uint?pointsId = scene.AddPoints(x, y);
                            if (pointsId.HasValue)
                            {
                                if (casingWidth > 0)
                                { // adds the casing
                                    scene.AddStyleLine(pointsId.Value, this.CalculateSceneLayer(OffsetCasing, zIndex),
                                                       minZoom, maxZoom, casingColor, width + (casingWidth * 2), lineJoin, dashes);
                                }
                                if (dashes == null)
                                { // dashes not set, use line offset.
                                    scene.AddStyleLine(pointsId.Value, this.CalculateSceneLayer(OffsetLine, zIndex),
                                                       minZoom, maxZoom, color, width, lineJoin, dashes);
                                }
                                else
                                { // dashes set, use line pattern offset.
                                    scene.AddStyleLine(pointsId.Value, this.CalculateSceneLayer(OffsetLinePattern, zIndex),
                                                       minZoom, maxZoom, color, width, lineJoin, dashes);
                                }

                                int    textColor;
                                int    fontSize;
                                string nameTag;
                                if (!rule.TryGetProperty("fontSize", out fontSize))
                                {
                                    fontSize = 10;
                                }
                                if (rule.TryGetProperty("text", out nameTag) &&
                                    rule.TryGetProperty("textColor", out textColor))
                                {
                                    int haloColor;
                                    int?haloColorNullable = null;
                                    if (rule.TryGetProperty("textHaloColor", out haloColor))
                                    {
                                        haloColorNullable = haloColor;
                                    }
                                    int haloRadius;
                                    int?haloRadiusNullable = null;
                                    if (rule.TryGetProperty("textHaloRadius", out haloRadius))
                                    {
                                        haloRadiusNullable = haloRadius;
                                    }
                                    string fontFamily;
                                    if (!rule.TryGetProperty("fontFamily", out fontFamily))
                                    {
                                        fontFamily = "Arial"; // just some default font.
                                    }
                                    string name;
                                    if (way.Tags.TryGetValue(nameTag, out name))
                                    {
                                        scene.AddStyleLineText(pointsId.Value, this.CalculateSceneLayer(OffsetLineText, zIndex),
                                                               minZoom, maxZoom, textColor, fontSize, name, fontFamily, haloColorNullable, haloRadiusNullable);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Translates a lineair ring.
        /// </summary>
        /// <param name="scene">The scene to add primitives to.</param>
        /// <param name="projection">The projection used to convert the objects.</param>
        /// <param name="lineairRing"></param>
        private void TranslateLineairRing(Scene2D scene, IProjection projection, LineairRing lineairRing)
        {
            // build the rules.
            List <MapCSSRuleProperties> rules =
                this.BuildRules(new MapCSSObject(lineairRing));

            // validate what's there.
            if (rules.Count == 0)
            {
                return;
            }

            // get x/y.
            double[] x = null, y = null;
            if (lineairRing.Coordinates != null &&
                lineairRing.Coordinates.Count > 0)
            { // pre-calculate x/y.
                x = new double[lineairRing.Coordinates.Count];
                y = new double[lineairRing.Coordinates.Count];
                for (int idx = 0; idx < lineairRing.Coordinates.Count; idx++)
                {
                    x[idx] = projection.LongitudeToX(
                        lineairRing.Coordinates[idx].Longitude);
                    y[idx] = projection.LatitudeToY(
                        lineairRing.Coordinates[idx].Latitude);
                }

                // simplify.
                if (x.Length > 2)
                {
                    double[][] simplified = SimplifyCurve.Simplify(new double[][] { x, y }, 0.0001);
                    x = simplified[0];
                    y = simplified[1];
                }
            }
            // add the z-index.
            foreach (var rule in rules)
            {
                float minZoom = (float)projection.ToZoomFactor(rule.MinZoom);
                float maxZoom = (float)projection.ToZoomFactor(rule.MaxZoom);

                int zIndex;
                if (!rule.TryGetProperty <int>("zIndex", out zIndex))
                {
                    zIndex = 0;
                }

                // interpret the results.
                if (x != null)
                { // there is a valid interpretation of this way.
                    int color;
                    int fillColor;
                    if (rule.TryGetProperty("fillColor", out fillColor))
                    { // render as an area.
                        float fillOpacity;
                        if (rule.TryGetProperty("fillOpacity", out fillOpacity))
                        {
                            SimpleColor simpleFillColor = new SimpleColor()
                            {
                                Value = fillColor
                            };
                            fillColor = SimpleColor.FromArgb((int)(255 * fillOpacity),
                                                             simpleFillColor.R, simpleFillColor.G, simpleFillColor.B).Value;
                        }
                        uint?pointsId = scene.AddPoints(x, y);
                        if (pointsId.HasValue)
                        {
                            scene.AddStylePolygon(pointsId.Value, this.CalculateSceneLayer(OffsetArea, zIndex), minZoom, maxZoom, fillColor, 1, true);
                            if (rule.TryGetProperty("color", out color))
                            {
                                scene.AddStylePolygon(pointsId.Value, this.CalculateSceneLayer(OffsetCasing, zIndex), minZoom, maxZoom, color, 1, false);
                            }
                        }
                    }
                }
            }
        }
        /// <summary>
        /// Translates OSM objects into basic renderable primitives.
        /// </summary>
        /// <param name="scene">The scene to add primitives to.</param>
        /// <param name="projection">The projection used to convert the objects.</param>
        /// <param name="osmGeo">The osm object.</param>
        /// <returns></returns>
        public override void Translate(Scene2D scene, IProjection projection, CompleteOsmGeo osmGeo)
        {
            // set the scene backcolor.
            scene.BackColor = this.GetCanvasColor().Value;

            if (osmGeo == null)
            {
                return;
            }

            // store the object count.
            int countBefore = scene.Count;

            // interpret the osm-objects.
            switch (osmGeo.Type)
            {
            case CompleteOsmType.Node:
                this.TranslateNode(scene, projection, osmGeo as CompleteNode);
                break;

            case CompleteOsmType.Way:
                this.TranslateWay(scene, projection, osmGeo as CompleteWay);
                break;

            case CompleteOsmType.Relation:
                this.TranslateRelation(scene, projection, osmGeo as CompleteRelation);
                break;

            case CompleteOsmType.ChangeSet:
                break;

            default:
                throw new ArgumentOutOfRangeException();
            }

            // interpret the osmGeo object and check if it makes up an area.
            GeometryCollection collection = _geometryInterpreter.Interpret(osmGeo);

            foreach (Geometry geometry in collection)
            {
                if (geometry is LineairRing)
                { // a simple lineair ring.
                    this.TranslateLineairRing(scene, projection, geometry as LineairRing);
                }
                else if (geometry is Polygon)
                { // a simple polygon.
                    this.TranslatePolygon(scene, projection, geometry as Polygon);
                }
                else if (geometry is MultiPolygon)
                { // a multipolygon.
                    this.TranslateMultiPolygon(scene, projection, geometry as MultiPolygon);
                }
            }

            // check if any objects have been added to the scene.
            if (scene.Count <= countBefore)
            { // no objects have been added. Apply default styles if needed.
                if (osmGeo.Type == CompleteOsmType.Node &&
                    _mapCSSFile != null &&
                    _mapCSSFile.DefaultPoints)
                { // apply default points style.
                    CompleteNode node    = (osmGeo as CompleteNode);
                    uint         pointId = scene.AddPoint(projection.LongitudeToX(node.Coordinate.Longitude), projection.LatitudeToY(node.Coordinate.Latitude));
                    scene.AddStylePoint(pointId, this.CalculateSceneLayer(OffsetPoint, 0), float.MinValue, float.MaxValue,
                                        SimpleColor.FromKnownColor(KnownColor.Black).Value, 2);
                }
                else if (osmGeo.Type == CompleteOsmType.Way &&
                         _mapCSSFile != null &&
                         _mapCSSFile.DefaultLines)
                { // apply default lines style.
                    CompleteWay way = (osmGeo as CompleteWay);
                    // get x/y.
                    double[] x = null, y = null;
                    if (x == null)
                    { // pre-calculate x/y.
                        x = new double[way.Nodes.Count];
                        y = new double[way.Nodes.Count];
                        for (int idx = 0; idx < way.Nodes.Count; idx++)
                        {
                            x[idx] = projection.LongitudeToX(
                                way.Nodes[idx].Coordinate.Longitude);
                            y[idx] = projection.LatitudeToY(
                                way.Nodes[idx].Coordinate.Latitude);
                        }

                        // simplify.
                        if (x.Length > 2)
                        {
                            double[][] simplified = SimplifyCurve.Simplify(new double[][] { x, y }, 0.0001);
                            x = simplified[0];
                            y = simplified[1];
                        }
                    }
                    uint?points = scene.AddPoints(x, y);
                    if (points.HasValue)
                    {
                        scene.AddStyleLine(points.Value, this.CalculateSceneLayer(OffsetLine, 0), float.MinValue, float.MaxValue,
                                           SimpleColor.FromKnownColor(KnownColor.Red).Value, 1, LineJoin.Round, null);
                    }
                }
            }
        }