Monday, 18 April 2016

Draw AutoCAD Polyline using Oracle Spatial Geometry

I found an interesting blog entry about drawing a polyline (straight line segments only) in AutoCAD by using Oracle Spatial Geometry. 

A quick test showed, that the SQL statement doesn't seem to be correct. It also uses an undocumented Oracle command which is not available anymore in 12c (according to this website).

I changed the SQL script using LISTAGG - which is only available in 11.2 and newer:


 -- only required if decimal separator is not a dot:  
 ALTER SESSION SET NLS_NUMERIC_CHARACTERS = '. ';  
   
 -- creates list of coordinates which can be easily copied and pasted into AutoCAD  
   
 SELECT  c.fid,   
 '_pline ' || LISTAGG(t.X || ',' || t.Y, ' ') WITHIN GROUP (order by t.id) as pline  
 FROM ww_line c, -- your table name  
 TABLE(SDO_UTIL.GETVERTICES(c.geom)) t -- your spatial column name  
 WHERE c.fid = 138900 -- your ID  
 GROUP BY c.fid;  

To finish the "pline" command you press "ESC". I don't know how to have something similar when you copy and paste the command into AutoCAD so I couldn't add it to the SQL result set.

1. run SQL script
2. copy result
3. paste result into AutoCAD
4. press "ESC" to finish pline command


Line in Oracle Spatial ( preview with Georaptor)

Line in AutoCAD

Map 2013, SP2

Tuesday, 29 March 2016

Plot : FDO hatch issue with rotated viewports

As mentioned previously, there is an issue when plotting FDO hatches in a rotated viewport.

It seems to occur with different hatch patterns (except solid) and with different plot devices (DWG to PDF, HP DesignJet, DWF). Here is an example - in some areas the hatch is missing:





As we found out, not all plot devices seem to have the problem. We are able to plot our layouts without this particular issue using Adobe PDF writer. 

Map 2013, SP2

Monday, 14 March 2016

ECW file header

There is a tool by Intergraph/ERDAS for modifying ECW headers called "ECW Header Editor". If you need to apply a new insertion point you can just open the ECW file, enter the new coordinates and save the changes back to the file.



Command line version is available as well. The tool is part of a package called "Apollo Essentials".

Current download URL : 

http://download.intergraph.com/downloads/erdas-apollo-essentials---image-web-server-2011-utilities-version-11.0.4


Wednesday, 9 March 2016

Create hard links for multiple files in batch process

If you need to create hard links for multiple files in one folder you can use the following batch script:

@ECHO OFF

set source_path="C:\Data\Hansa\Befliegung\Mosaic_rgb\"
set source_filter="*.tif"
set target_path="C:\raster\2013\"

for  %%i in (%source_path%%source_filter%) do (mklink /h %target_path%%%~nxi %source_path%%%~nxi)

pause

The script needs to be run with Administrator privileges.

LV95 Reference frame - world files for raster

If you need to transform world files as part of the the reference frame change (LV03 --> LV95) - I have written a small tool which does the transformation, either by performing a transformation using REFRAME or by performaning a simple translation. Let me know if the tool would be helpful - I can send it by email.



Oracle - ORDER BY for numeric values in VARCHAR2 field

We have an address list where the housenumber is stored as VARCHAR2. The house number is not always a number. Here a few examples:

---
  21-25   
 54-58, 66-70, 76-78
1
...
10
10a-10b
10b
102
102-104
10-34
...
12 / 14
...
128
1+3
...
2
2 - 10
2, 4, 6
2a-2c
2b / 2c
20

What all data sets have in common is that the first characters are always digits. To order all data sets numerically using the housenumber the following SQL can be used

select strassennr, 
from wfe_sk_einsatzplan 
order by  lpad(regexp_substr(strassennr,'(([0-9]+))'),20);

Which returns a list like:

1
1 - 49
...
1-19
1+3
1
1-7
1
1
1-5
1
...
1
1-7
1-11
1-3
...
1-3
1-7
1
1+3a
2, 4, 6
2
2b / 2c
2
2-12
2-12
2
2-4
2-12
2
2+8
..
2-6, 7-23
2-6c
..
2a-2c
...

REGEXP_SUBSTR extracts the first numeric characters we need for sorting. As the result is still of type VARCHAR2 LPAD makes sure, that they are ordered accordingly (see here).

Alternative: to_number(regexp_substr(strassennr,'(([0-9]+))'));

The report now shows the datasets in an order the user expects:
datasets ordered by house number

Monday, 1 February 2016

Map-Plot - batch plotting

If you use Map plot module (Industry Model extension) you can generate maps in a batch process.
Unfortunately we always get an unspecific error message when executing the batch plot module - something along the lines of 

"Plot was not configured correctly"

Funny though as you can generate the preview and plot the preview without any issue.

To get our 400+ maps plotted I wrote a quick'ndirty batch plot plugin myself. It does its job - maybe it is useful for someone else. Most bits are taken from Map development sample or through-the-interface blog. I also copied some bits from another older plugin - that's why a class is called "Inselplan" but has nothing to with it.

Some notes:

- on one PC generating a PLT file took roughly 1 minute per map, on a second PC roughly 5 minutes per map. Both PCs have nearly the same specs and software installed. 
- there is an issue with FDO hatches - if the viewport is rotated some hatches (it seems most hatches except solid) have artifacts (such as a white rectangular or hatches occuring twice but slightly shifted).


Hatch issue - FDO hatches, viewport rotated
Map 2013, SP2

source code:


 using System;  
 using Autodesk.Map.IM.Forms;  
 using Autodesk.Map.IM.Plot.Application;  
 using Autodesk.Map.IM.Plot.Components.PlotLibrary;  
 using Autodesk.Map.IM.Plot.Components.PlotLibrary.ContextMenu;  
 using Autodesk.Map.IM.Plot.Data;  
 using Autodesk.Map.IM.Plot.Components.PlotLibrary.Command;  
 namespace Inselplan_Batch  
 {  
   /// <summary>  
   /// <para>  
   /// Our document plugin - its priority has to be set so it will be loaded after the main plot  
   /// plugin - see this example's TBP file.   
   /// </para>  
   /// <para>  
   /// </para>  
   /// </summary>  
   public class DocumentPlugin : DocumentPlugIn  
   {  
     /// <summary>  
     /// Called by the TB Forms framework - we will use it to initialize our plugin  
     /// </summary>  
     /// <param name="sender"></param>  
     /// <param name="e"></param>  
     public override void OnInitComplete(object sender, EventArgs e)  
     {  
       base.OnInitComplete(sender, e);  
       //get the [PlotFlyIn] for our TB document...  
       PlotFlyIn currentPlotFlyIn = PlotFlyIn.GetCurrent(this.Document);  
       //...which can be [null], if it has been excluded in TB Admin  
       if (null != currentPlotFlyIn)  
       {  
         //connect to the [PlotExtensionInitialized] event - it will be raied by the respective  
         //flyin after the library (TreeView) has been fully loaded in response to  
         //the [WorkspaceOpened] event - now the Plot API can be used  
         currentPlotFlyIn.PlotExtensionInitialized += this.HandlePlotExtensionInitialized;  
       }  
     }  
     private void HandlePlotExtensionInitialized(object sender, EventArgs e)  
     {  
       //get the [PlotFlyIn]...  
       PlotFlyIn plotFlyIn = (PlotFlyIn)sender;  
       //...and the PltPlotLibrary (which is a TreeView)...  
       PltPlotLibrary plotLibrary = plotFlyIn.PltPlotLibrary;  
       //...so we can add our menu entries to the the plot-specific context menu via the library's [PltContextMenuFactory]  
       PltContextMenuFactory contextMenuFactory = plotLibrary.ContextMenuFactory;  
       //That is, it won't be available for templates or groups  
       //context menus are available for the following types:  
       // - IPlot  
       // - IPlotTemplate  
       // - IPlotGroup  
       // - IPlotTemplateGroup  
       // nicht auf Template setzen, der neue Plot wird in der Gruppe gespeichert in der Befehl aufgerufen wird  
       // - das geht aber nicht bei Templates und führt zum Absturz  
       PltCommonContextMenuStrip contextMenuStrip = contextMenuFactory.GetMenuForLibraryType(typeof(IPlotGroup));  
       //every command has its key - the same command (class) key is not allowed to be added  
       //twice to the same context menu  
       string editCommandKey = PltCommand.GetDefaultCommandKey(typeof(PltEditCommand));  
       //the plot context menu contains the [PltEditCommand] command. Get its  
       //insertion index...  
       int editCommandIndex = contextMenuStrip.Items.IndexOfKey(editCommandKey);  
       //...so we can then add our own commands right behind it  
       contextMenuStrip.InsertMenuItem(new Inselplanerstellung(plotLibrary), editCommandIndex + 1);  
       //contextMenuStrip.InsertMenuItem(new MyFastEditCommand2(plotLibrary), editCommandIndex + 2);  
       //alternatively, one can also use the [AddMenuItem] method which adds the command  
       //as the last item - retrieving another command's key + index first is not required then  
       //  
       //contextMenuStrip.AddMenuItem(new MyFastEditCommand2(this.Document, plotLibrary));  
     }  
   }  
 }  
 
 using Inselplan_Batch.Properties;  
 using Autodesk.Map.IM.Plot.Api;  
 using Autodesk.Map.IM.Plot.Components.PlotLibrary;  
 using Autodesk.Map.IM.Plot.Components.PlotLibrary.Command;  
 using Autodesk.Map.IM.Plot.Components.Runtime;  
 using Autodesk.Map.IM.Plot.Components;  
 using Autodesk.Map.IM.Plot.Data;  
 using Autodesk.Map.IM.Data;  
 using Autodesk.Map.IM.PlaneGeometry;  
 using System.Windows.Forms;  
 using System.Collections;  
 using System.Collections.Generic;  
 using Autodesk.AutoCAD.Runtime;  
 using Autodesk.AutoCAD.EditorInput;  
 using Autodesk.AutoCAD.ApplicationServices;  
 using Autodesk.AutoCAD.DatabaseServices;  
 using Autodesk.AutoCAD.PlottingServices;  
 namespace Inselplan_Batch  
 {  
   /// <summary>  
   /// </summary>  
   /// <remarks>  
   /// </remarks>  
   [PltPrerequisiteSingleNodeSelected]  
   public class Inselplanerstellung : PltCommand  
   {  
     /// <summary>  
     /// Ctor - it requires the caller to pass a valid <see cref="PltPlotLibrary"/> instance.  
     /// </summary>  
     /// <param name="library"></param>  
     public Inselplanerstellung(PltPlotLibrary library)  
       : base(library, Resources.command)  
     {  
     }  
     /// <summary>  
     /// Called by the Plot API after the user has selected our context menu entry.  
     /// </summary>  
     protected override void ExecuteCommand()  
     {  
       try  
       {  
         //welche gruppe wurde ausgewhlt?  
         IPlotLibraryGroup groupP = this.GetGroup(this.GetFirstTreeNode(this.PlotLibrary.SelectedItems));  
         //PlotLibrary ist die grafische Repräsentation - das Plot-Flyin/Baumstruktur  
         IPlotConnection plotConnection = this.PlotLibrary.Connection;  
         IPlotLibrary myLibrary = plotConnection.Library;  
         //PltPlotLibrary myPltPlotLibrary = this.PlotLibrary;  
         // Zum Speichern der neuen Plots wird die gruppe verwendet,  
         // aus der der Befehl heraus aufgerufen wurde.  
         //Gruppe also vorher manuell anlegen  
         IPlotLibraryGroup group = this.GetGroup(this.GetFirstTreeNode(this.PlotLibrary.SelectedItems));  
         IPlotGroup plotGroup = group as IPlotGroup;  
         //wird benötigt zum Pdate von PLT_PLOT  
         TBConnection myConnection = plotConnection.Connection;  
         //die beiden Views  
         FeatureClass tabPLT_PLOT_GROUP_PLOT;  
         string tabName_PLT_Plot_GROUP_PLOT = "PLT_PLOT_GROUP_PLOT";  
         string PlotTabelle = "PLT_PLOT";  
         Autodesk.Map.IM.Data.FeatureClass PlotFeatureClass = myConnection.FeatureClasses[PlotTabelle];  
         tabPLT_PLOT_GROUP_PLOT = myConnection.FeatureClasses[tabName_PLT_Plot_GROUP_PLOT];  
          //FID der Plotgruppe?  
         string SQLAbfrage = "select fid from plt_plot_group where group_name = '" + groupP.Name + "'";  
         //this.Application.MessageBox(SQLAbfrage, "Bericht - Hinweis1");  
         Autodesk.Map.IM.Data.Tools.RecordReader reader = new Autodesk.Map.IM.Data.Tools.RecordReader(myConnection);  
         reader.Open(SQLAbfrage);  
         reader.Read();  
         long? FIDPlotGruppe = reader.LngValue("FID");  
         reader.Close();  
         //FID der Plotgruppe, in die geplottet Plots verschoben werden sollen          
         string SQLAbfrage2 = "select fid from plt_plot_group where group_name = 'Grundbuchpläne_geplottet'";  
         //this.Application.MessageBox(SQLAbfrage, "Bericht - Hinweis1");  
         Autodesk.Map.IM.Data.Tools.RecordReader reader2 = new Autodesk.Map.IM.Data.Tools.RecordReader(myConnection);  
         reader.Open(SQLAbfrage2);  
         reader.Read();  
         long? FIDPlotGruppeVerschieben = reader.LngValue("FID");  
         reader.Close();  
         if (FIDPlotGruppe == null)  
         {  
           MessageBox.Show("Zu Plotgruppe '" + groupP.Name + "' keine FID gefunden!");  
         }  
         DialogResult result1 = MessageBox.Show("Achtung: Job Status richtig gesetzt?\nBatchPlot für Gruppe: '" + groupP.Name + "' (FID=" + FIDPlotGruppe+")?",  
         "BatchPlot starten?",  
         MessageBoxButtons.YesNo);  
         if (result1 != DialogResult.Yes)  
           return;  
         // alle Plots durchgehen  
         foreach (Feature feature in tabPLT_PLOT_GROUP_PLOT.GetFeatures(true))  
         {  
           long FIDPlot = feature.Attributes.Get("FID_PLOT").ValueLong;  
           long FIDPlotGroup = feature.Attributes.Get("FID_PLOT_GROUP").ValueLong;  
           //Gruppe "Test"  
           if (FIDPlotGroup != FIDPlotGruppe)  
             continue;  
           IPlot plot = myLibrary.FindPlot(FIDPlot);  
           PltPlotRenderer r = PltPlotRendererFactory.CreateRenderer(RendererConfiguration.Plot, plot, RenderingCompatibility.CurrentVersion);  
           r.Render();  
           bool plotResult = SimplePlot();  
           // DWG schliessen  
           Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument.CloseAndDiscard();  
           //Plot in anderen Ordner verschieben  
           if (FIDPlotGruppeVerschieben != null)  
           {  
             Autodesk.Map.IM.Data.Tools.RecordWriter writer = new Autodesk.Map.IM.Data.Tools.RecordWriter(myConnection, "plt_plot_group_plot");  
             writer.Clear();  
             // Key - wird als WHERE Clause für das Update verwendet (...where user_id = userid)  
             writer.WriteKey("fid_plot", FIDPlot);  
             // neuer Werte  
             long lFID = (long)FIDPlotGruppeVerschieben;  
             writer.Write("fid_plot_group", lFID);  
             // Update ausführen  
             writer.Update();  
           }  
         }  
       }  
       catch (ConnectionInvalidatedException ex)  
       {  
         MessageBox.Show("Plot Connection ungültigt. \n" + ex.Message.ToString());  
       }  
     }  
      public bool SimplePlot()  
     {  
       Document doc =  
        Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;  
       Editor ed = doc.Editor;  
       Database db = doc.Database;  
       Transaction tr =  
        db.TransactionManager.StartTransaction();  
       using (tr)  
       {  
         // We'll be plotting the current layout  
         BlockTableRecord btr =  
          (BlockTableRecord)tr.GetObject(  
           db.CurrentSpaceId,  
           OpenMode.ForRead  
          );  
         Layout lo =  
          (Layout)tr.GetObject(  
           btr.LayoutId,  
           OpenMode.ForRead  
          );  
         // We need a PlotInfo object  
         // linked to the layout  
         PlotInfo pi = new PlotInfo();  
         pi.Layout = btr.LayoutId;  
         // We need a PlotSettings object  
         // based on the layout settings  
         // which we then customize  
         PlotSettings ps =  
          new PlotSettings(lo.ModelType);  
         ps.CopyFrom(lo);  
         // The PlotSettingsValidator helps  
         // create a valid PlotSettings object  
         PlotSettingsValidator psv =  
          PlotSettingsValidator.Current;  
         // We'll plot the extents, centered and  
         // scaled to fit  
         /*  
         psv.SetPlotType(  
          ps,  
          Autodesk.AutoCAD.DatabaseServices.PlotType.Extents  
         );  
         psv.SetUseStandardScale(ps, true);  
         psv.SetStdScaleType(ps, StdScaleType.ScaleToFit);  
         psv.SetPlotCentered(ps, true);  
          * */  
         // We'll use the standard DWF PC3, as  
         // for today we're just plotting to file  
         /*  
         psv.SetPlotConfigurationName(  
          ps,  
          "DWF6 ePlot.pc3",  
          "ANSI_A_(8.50_x_11.00_Inches)"  
         );  
          * */  
         // We need to link the PlotInfo to the  
         // PlotSettings and then validate it  
         pi.OverrideSettings = ps;  
         PlotInfoValidator piv =  
          new PlotInfoValidator();  
         piv.MediaMatchingPolicy =  
          MatchingPolicy.MatchEnabled;  
         piv.Validate(pi);  
         // A PlotEngine does the actual plotting  
         // (can also create one for Preview)  
         if (PlotFactory.ProcessPlotState ==  
           ProcessPlotState.NotPlotting)  
         {  
           PlotEngine pe =  
            PlotFactory.CreatePublishEngine();  
           using (pe)  
           {  
             // Create a Progress Dialog to provide info  
             // and allow thej user to cancel  
             PlotProgressDialog ppd =  
              new PlotProgressDialog(false, 1, true);  
             using (ppd)  
             {  
               ppd.set_PlotMsgString(  
                PlotMessageIndex.DialogTitle,  
                "Custom Plot Progress"  
               );  
               ppd.set_PlotMsgString(  
                PlotMessageIndex.CancelJobButtonMessage,  
                "Cancel Job"  
               );  
               ppd.set_PlotMsgString(  
                PlotMessageIndex.CancelSheetButtonMessage,  
                "Cancel Sheet"  
               );  
               ppd.set_PlotMsgString(  
                PlotMessageIndex.SheetSetProgressCaption,  
                "Sheet Set Progress"  
               );  
               ppd.set_PlotMsgString(  
                PlotMessageIndex.SheetProgressCaption,  
                "Sheet Progress"  
               );  
               ppd.LowerPlotProgressRange = 0;  
               ppd.UpperPlotProgressRange = 100;  
               ppd.PlotProgressPos = 0;  
               // Let's start the plot, at last  
               ppd.OnBeginPlot();  
               ppd.IsVisible = true;  
               pe.BeginPlot(ppd, null);  
               // We'll be plotting a single document  
               pe.BeginDocument(  
                pi,  
                doc.Name,  
                null,  
                1,  
                true, // Let's plot to file  
                "c:\\temp\\"+lo.LayoutName  
               );  
               // Which contains a single sheet  
               ppd.OnBeginSheet();  
               ppd.LowerSheetProgressRange = 0;  
               ppd.UpperSheetProgressRange = 100;  
               ppd.SheetProgressPos = 0;  
               PlotPageInfo ppi = new PlotPageInfo();  
               pe.BeginPage(  
                ppi,  
                pi,  
                true,  
                null  
               );  
               pe.BeginGenerateGraphics(null);  
               pe.EndGenerateGraphics(null);  
               // Finish the sheet  
               pe.EndPage(null);  
               ppd.SheetProgressPos = 100;  
               ppd.OnEndSheet();  
               // Finish the document  
               pe.EndDocument(null);  
               // And finish the plot  
               ppd.PlotProgressPos = 100;  
               ppd.OnEndPlot();  
               pe.EndPlot(null);  
             }  
           }            
           return true;  
         }  
         else  
         {  
           ed.WriteMessage(  
            "\nAnother plot is in progress."  
           );  
           return false;  
         }  
       }  
     }  
   }  
 }