Best Text Position in Polygon

Problem:
You have many polygons and you want place some texts in each of them. polygons may have different shapes. they may be concave polygons. How you can find best place for your text?
Answer:
My Solution is as follows:
1- Buffer Polygon to inside of it. I mean Buffer (Offset)  Polygon with a negative number. Do This until you get a triangle or a Condition that confirm this step iterate many times. for example area of offset polygon become many times smaller that original polygon
2-compute centroid of this new polygon. this is the best place
3- Compute Azimuth of Longest Side of This polygon and this is the best orientation of the text

I wrote a C# function that take polygon and gave best text position and best text orientation.

It is a console application that needs some dotspatial dll's  as reference.

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;
using DotSpatial.Topology;
using DotSpatial.Data;
using DotSpatial.Symbology;

 

namespace bestTextPos_Console
{
    class Program
    {

        static void Main()
        {
            string rPath = @"c:\TEMP\shape570403.txt";
            string wPath = @"c:\TEMP\shape570403_cent.txt";

            try
            {
                //StreamWriter sw = new StreamWriter(wPath);
                // StreamReader sr = new StreamReader(rPath);
                DataTable dt = new DataTable();
                dt = ReadFile(rPath, ",", new string[] { "x", "y" });
                makepoly(dt, wPath);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
        public static DataTable ReadFile(string filename, string delimiter)
        {
            return ReadFile(filename, delimiter, null);
        }
        /// <summary>
        /// Read the file and return a DataTable
        /// </summary>
        /// <param name="filename">File to read</param>
        /// <param name="delimiter">Delimiting string</param>
        /// <param name="columnNames">Array of column names</param>
        /// <returns>Populated DataTable</returns>
        public static DataTable ReadFile(string filename, string delimiter, string[] columnNames)
        {
            //  Create the new table
            DataTable data = new DataTable();
            data.Locale = System.Globalization.CultureInfo.CurrentCulture;

            //  Check file
            if (!File.Exists(filename))
                throw new FileNotFoundException("File not found", filename);

            //  Process the file line by line
            string line;
            using (TextReader tr = new StreamReader(filename, Encoding.Default))
            {
                //  If column names were not passed, we'll read them from the file
                if (columnNames == null)
                {
                    //  Get the first line
                    line = tr.ReadLine();
                    if (string.IsNullOrEmpty(line))
                        throw new IOException("Could not read column names from file.");
                    columnNames = line.Split(new string[] { delimiter }, StringSplitOptions.RemoveEmptyEntries);
                }

                //  Add the columns to the data table
                foreach (string colName in columnNames)
                    data.Columns.Add(colName);

                //  Read the file
                string[] columns;
                while ((line = tr.ReadLine()) != null)
                {
                    columns = line.Split(new string[] { delimiter }, StringSplitOptions.None);
                    //  Ensure we have the same number of columns
                    if (columns.Length != columnNames.Length)
                    {
                        string message = "Data row has {0} columns and {1} are defined by column names.";
                        throw new DataException(string.Format(message, columns.Length, columnNames.Length));
                    }
                    data.Rows.Add(columns);
                }
            }
            return data;

        }
        private static void makepoly(DataTable dt, string wPath)
        {
            try
            {

           
            Coordinate[] coord = new Coordinate[dt.Rows.Count];
            int i, count = 0;
            //DataGridViewRow dtRow;

            //foreach (DataRow  row in dt.Rows )
            //{
            //    coord[i] = new Coordinate(Convert.ToDouble(row[0]), Convert.ToDouble(row[1]));
            //}
            for (i = 0; i < dt.Rows.Count; i++)
            {
                DataRow row = dt.Rows[i];
                coord[i] = new Coordinate(Convert.ToDouble(row[0]), Convert.ToDouble(row[1]));

            }


            Feature f = new Feature();
            FeatureSet fs = new FeatureSet(/*f.FeatureType*/FeatureType.Polygon);


            Polygon poly = new Polygon(coord);
            fs.Features.Add(poly);

            double[] d = new double[fs.Features[0].Coordinates.Count];
            for (i = 0; i < fs.Features[0].Coordinates.Count - 1; i++)
            {
                d[i] = fs.Features[0].Coordinates[i].Distance(fs.Features[0].Coordinates[i + 1]);
            }

            //fs.SaveAs("C:\\Temp\\test.shp", true);
            IFeatureSet iF;
            //iF.SaveAs("C:\\Temp\\testb.shp", true);
            double bufferSteps = Math.Max(d.Max() / 1000, 1);
            double buffmarg = bufferSteps;
            do
            {
                iF = fs.Buffer(buffmarg -= bufferSteps, true);

            } while (iF.Vertex.Length > 8 && iF.Features[0].Area() > fs.Features[0].Area() / 100);


            while (iF.Vertex.Length < 7)
            {
                iF = fs.Buffer(buffmarg += bufferSteps, true);
            }

            double[] di = new double[iF.Features[0].Coordinates.Count];
            double[] azi = new double[iF.Features[0].Coordinates.Count];

            for (i = 0; i < iF.Features[0].Coordinates.Count - 1; i++)
            {
                di[i] = iF.Features[0].Coordinates[i].Distance(iF.Features[0].Coordinates[i + 1]);
                azi[i] = Azimuth(iF.Features[0].Coordinates[i], iF.Features[0].Coordinates[i + 1]);
            }

            double textAzimuth = azi[di.ToList().IndexOf(di.Max())];
            Triangle tr = new Triangle(iF.Features[0].Coordinates[0], iF.Features[0].Coordinates[1], iF.Features[0].Coordinates[2]);
            Coordinate centroid = tr.InCentre;
            File.WriteAllText(wPath, string.Format("X={0:0.##} ,Y={1:0.##}  ", centroid.X, centroid.Y) + string.Format("Az={0:0.00} Deg", textAzimuth));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

        }
        private static double Azimuth(Coordinate coordinate0, Coordinate coordinate1)
        {
            try
            {
                double x0 = coordinate0.X;
                double y0 = coordinate0.Y;
                double x1 = coordinate1.X;
                double y1 = coordinate1.Y;
                var longitudinalDifference = x1 - x0;
                var latitudinalDifference = y1 - y0;
                var azimuth = (Math.PI * .5d) - Math.Atan(latitudinalDifference / longitudinalDifference);
                if (longitudinalDifference > 0) return azimuth * 180 / Math.PI;
                else if (longitudinalDifference < 0) return (azimuth + Math.PI) * 180 / Math.PI;
                else if (latitudinalDifference < 0) return Math.PI * 180 / Math.PI; return 0d;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return 0;
            }
        }
    }
}

 

 

Input Sample File for example saved as c:\temp\Coords.txt.

-51760650.4037,78436159.4219
32680692.4478,74849161.2553
32680692.4478,74849161.2553
33984805.8509,-26238968.8951
33984805.8509,-26238968.8951
-73278521.5550,-31456420.7738
-73278521.5550,-31456420.7738
-51760650.4037,78436159.4219

The result is X,Y of  Text an its Azimuth (Orientation).

 

Go Back

Comment