Package org.locationtech.udig.printing.ui.pdf

Source Code of org.locationtech.udig.printing.ui.pdf.SivecoExportMapToImageWizard

/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2004-2013, 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.printing.ui.pdf;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.locationtech.udig.catalog.CatalogPlugin;
import org.locationtech.udig.catalog.ICatalog;
import org.locationtech.udig.catalog.ID;
import org.locationtech.udig.catalog.IGeoResource;
import org.locationtech.udig.catalog.IService;
import org.locationtech.udig.catalog.IServiceFactory;
import org.locationtech.udig.core.internal.CorePlugin;
import org.locationtech.udig.mapgraphic.style.LocationStyleContent;
import org.locationtech.udig.project.IMap;
import org.locationtech.udig.project.internal.Layer;
import org.locationtech.udig.project.internal.LayerFactory;
import org.locationtech.udig.project.internal.Map;
import org.locationtech.udig.project.render.RenderException;
import org.locationtech.udig.project.ui.ApplicationGIS;
import org.locationtech.udig.project.ui.BoundsStrategy;
import org.locationtech.udig.project.ui.SelectionStyle;
import org.locationtech.udig.project.ui.ApplicationGIS.DrawMapParameter;
import org.locationtech.udig.project.ui.internal.Messages;
import org.locationtech.udig.project.ui.internal.ProjectUIPlugin;
import org.locationtech.udig.project.ui.wizard.export.image.Image2Pdf;
import org.locationtech.udig.project.ui.wizard.export.image.ImageExportPage;
import org.locationtech.udig.project.ui.wizard.export.image.MapSelectorPageWithScaleColumn;
import org.locationtech.udig.project.ui.wizard.export.image.PDFImageExportFormat;
import org.locationtech.udig.project.ui.wizard.export.image.Paper;
import org.locationtech.udig.project.ui.wizard.export.image.XAffineTransform;
import org.locationtech.udig.style.sld.editor.DialogSettingsStyleContent;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.ui.IExportWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.XMLMemento;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.factory.GeoTools;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;

/**
* Wizard for exporting a collection of maps to a collection of images.  One for each map.
*
* @author Not Jesse
*/
public class SivecoExportMapToImageWizard extends Wizard implements IExportWizard {

  public static final String DIRECTORY_KEY = "exportDirectoryKey"; //$NON-NLS-1$
  public static final String FORMAT_KEY = "exportFormatKey"; //$NON-NLS-1$
  public static final String WIDTH_KEY = "widthKey"; //$NON-NLS-1$
  public static final String HEIGHT_KEY = Messages.ExportMapToImageWizard_3;
    public static final String SELECTION = "SELECTION_HANDLING"; //$NON-NLS-1$

    private static final String FEATURE_ID_KEY = "FEATURE_ID";
   
  private ImageExportPage imageSettingsPage = new ImageExportPage();
  private MapSelectorPageWithScaleColumn mapSelectorPage = new MapSelectorPageWithScaleColumn("Map Select", null, null);

  public SivecoExportMapToImageWizard() {
    setWindowTitle(Messages.ExportMapToImageWizard_windowtitle);
    setDialogSettings(ProjectUIPlugin.getDefault().getDialogSettings());
    addPage(mapSelectorPage);
    addPage(imageSettingsPage);
    setNeedsProgressMonitor(true);
  }

  /**
   * Utility method to export a given map to a PDF according to the
   * passed parameters.
   */
  public static void exportSivecoMap(IMap map, int dpi, Paper paper, File dest, SelectionStyle selectionStyle, IProgressMonitor monitor)
      throws RenderException, IOException, TransformException, NoninvertibleTransformException {
     
      final boolean isLandscape = true;
      final int pageMarginPixels = 10;
     
      //check preconditions
        if (map == null) {
            throw new IllegalArgumentException("map must not be null"); //$NON-NLS-1$
        }
      if (dest == null) {
            throw new IllegalArgumentException("dest must not be null"); //$NON-NLS-1$
        }
       if (dpi <= 0) {
            throw new IllegalArgumentException("dpi must be > 0"); //$NON-NLS-1$
        }
        if (paper == null) {
            throw new IllegalArgumentException("paper must not be null"); //$NON-NLS-1$
        }     
       
        monitor.beginTask(Messages.ExportMapToImageWizard_RenderingTaskName, 3);
        String pattern = Messages.ExportMapToImageWizard_preparingTaskName;
        Object[] args = new Object[]{map.getName()};
        monitor.setTaskName(MessageFormat.format(pattern, args));
       
        Dimension contentDim =
            new Dimension(
                paper.getPixelWidth(isLandscape, dpi) - (2 * pageMarginPixels),
                paper.getPixelHeight(isLandscape, dpi) - (2 * pageMarginPixels)
                );
       
        BufferedImage image = new BufferedImage(contentDim.width,
                                                contentDim.height,
                                                BufferedImage.TYPE_INT_ARGB);

        Graphics2D g = image.createGraphics();
       
        try {
            monitor.worked(1);
            pattern = Messages.ExportMapToImageWizard_renderingTaskname;
            args = new Object[]{map.getName()};
            monitor.setTaskName(MessageFormat.format(pattern, args));
           
            int scaleDenom = MapSelectorPageWithScaleColumn.getScaleDenom(map);
            BoundsStrategy boundsStrategy = new BoundsStrategy(scaleDenom);
           
            //final Map mapCopy = (Map) EcoreUtil.copy((EObject) map);
            final Map mapCopy = (Map) ApplicationGIS.copyMap(map);
            List<Layer> layersInternal = mapCopy.getLayersInternal();           
            List<Layer> mapGraphics = prepareMapGraphics(mapCopy, contentDim, monitor);
            layersInternal.addAll(mapGraphics);
           
            //block out the labels over my whitebox
            //context.getLabelS...()
           
            //add decorator with some basic information about the selected feature
            /*
            Rectangle detailsBox = new Rectangle(whiteboxLoc.x, whiteboxLoc.y, whiteboxLoc.width, (int)(whiteboxLoc.height * 0.25));
            addSelectedFeatureDetails(mapCopy, detailsBox);
            */
           
            DrawMapParameter drawMapParameter =
                new DrawMapParameter(g,
                                     new java.awt.Dimension(contentDim.width, contentDim.height),
                                     mapCopy,
                                     boundsStrategy,
                                     dpi,
                                     selectionStyle,
                                     monitor);
           
            ApplicationGIS.drawMap(drawMapParameter);
        }
        finally {
            g.dispose();
        }
       
        monitor.worked(1);
        pattern = Messages.ExportMapToImageWizard_writingTaskname;
        args = new Object[]{map.getName()};
        monitor.setTaskName(MessageFormat.format(pattern, args));
       
        Image2Pdf.write(image,
                        dest.getAbsolutePath(),
                        paper,
                        10, /*top and bottom margin*/
                        10, /*left and right margin*/
                        isLandscape,
                        dpi);
     
        monitor.done();
       
  }
 
  /*
   * (non-Javadoc)
   *
   * @see org.eclipse.jface.wizard.Wizard#performFinish()
   */
  @Override
  public boolean performFinish() {

    final Collection<String> errors = new ArrayList<String>();
    final Collection<IMap> renderedMaps = new ArrayList<IMap>();
    try {
      getContainer().run(false, true, new IRunnableWithProgress() {

        public void run(IProgressMonitor monitor)
            throws InvocationTargetException, InterruptedException {

          Collection<IMap> maps = mapSelectorPage.getMaps();

          monitor.beginTask(Messages.ExportMapToImageWizard_exportMapsTaskName, maps.size() * 3 + 1);
          monitor.worked(1);
          for (IMap map : maps) {
            try {
              exportMap(map, new SubProgressMonitor(monitor, 3));
            } catch (RenderException e) {
             
              Object[] args = new Object[]{map.getName(), e.getLocalizedMessage()};
              String pattern = Messages.ExportMapToImageWizard_renderingErrorMessage;
              errors.add(MessageFormat.format(pattern, args));
            } catch (IOException e) {
              errors
                  .add(Messages.ExportMapToImageWizard_ioexceptionErrorMessage
                      + e.getLocalizedMessage());
            } catch (TransformException e) {
              errors.add("Failed to create world file.  This image can not be used as a Raster file in uDig");
            } catch (NoninvertibleTransformException e) {
              errors.add("Failed to create world file.  This image can not be used as a Raster file in uDig");
            }
            renderedMaps.add(map);
          }
          mapSelectorPage.getMaps().remove(renderedMaps);
          mapSelectorPage.updateMapList();
        }
      });
    } catch (InvocationTargetException e) {
      throw new RuntimeException(e.getMessage(), e);
    } catch (InterruptedException e) {
      throw new RuntimeException(e.getMessage(), e);
    }

    if( !errors.isEmpty() ){
      ((WizardPage)getContainer().getCurrentPage()).setErrorMessage(Messages.ExportMapToImageWizard_ShowErrorMessage+errors.iterator().next());
      return false;
    }
   
    getDialogSettings().put(DIRECTORY_KEY,
        mapSelectorPage.getExportDir().getAbsolutePath());
    getDialogSettings().put(FORMAT_KEY, imageSettingsPage.getFormat().getName());

    return true;
  }

  private void exportMap(IMap map, IProgressMonitor monitor)
      throws RenderException, IOException, TransformException, NoninvertibleTransformException {

      if (imageSettingsPage.isPDF()) {
          exportSivecoMap(map,
                          imageSettingsPage.getFormat().getDPI(),
                          ((PDFImageExportFormat) imageSettingsPage.getFormat()).paper(),
                          determineDestinationFile(map),
                          imageSettingsPage.getSelectionHandling(),
                          monitor);
          return;
      }
     
    monitor.beginTask(Messages.ExportMapToImageWizard_RenderingTaskName, 3);
    String pattern = Messages.ExportMapToImageWizard_preparingTaskName;
    Object[] args = new Object[]{map.getName()};
    monitor.setTaskName(MessageFormat.format(pattern, args));
    File destination = determineDestinationFile(map);
    if (destination == null) {
      return;
    }
   
    double mapheight = map.getViewportModel().getHeight();
        double mapwidth = map.getViewportModel().getWidth();
        int width = imageSettingsPage.getWidth(mapwidth, mapheight);
    int height = imageSettingsPage.getHeight(mapwidth, mapheight);

   
    BufferedImage image = new BufferedImage(width, height,
        BufferedImage.TYPE_INT_ARGB);

    Graphics2D g = image.createGraphics();
   
    IMap renderedMap;
    try {
      monitor.worked(1);
      pattern = Messages.ExportMapToImageWizard_renderingTaskname;
      args = new Object[]{map.getName()};
      monitor.setTaskName(MessageFormat.format(pattern, args));
      int scaleDenom = MapSelectorPageWithScaleColumn.getScaleDenom(map);
      BoundsStrategy boundsStrategy = new BoundsStrategy(scaleDenom);
     
      //final Map mapCopy = (Map) EcoreUtil.copy((EObject) map);
      final Map mapCopy = (Map) ApplicationGIS.copyMap(map);
       
      DrawMapParameter drawMapParameter =
          new DrawMapParameter(g,
                               new java.awt.Dimension(width, height),
                               mapCopy,
                               boundsStrategy,
                               imageSettingsPage.getFormat().getDPI(),
                               imageSettingsPage.getSelectionHandling(),
                               monitor);
     
      renderedMap = ApplicationGIS.drawMap(drawMapParameter);
    }
    finally {
      g.dispose();
    }
    monitor.worked(1);
    pattern = Messages.ExportMapToImageWizard_writingTaskname;
    args = new Object[]{map.getName()};
    monitor.setTaskName(MessageFormat.format(pattern, args));
    imageSettingsPage.getFormat().write(renderedMap, image, destination);
    String baseFile = destination.getPath().substring(0, destination.getPath().lastIndexOf("."));
    createWorldFile(renderedMap.getViewportModel().worldToScreenTransform().createInverse(), baseFile);
    createProjectionFile(baseFile, renderedMap.getViewportModel().getCRS());

    addToCatalog(destination);
   
    monitor.done();
  }

  private static List<Layer> prepareMapGraphics(Map map, Dimension contentDim, IProgressMonitor monitor) {
     
      List<Layer> mapGraphics = new ArrayList<Layer>();
     
      //whitebox
      Rectangle whiteboxLoc = new Rectangle((int)(contentDim.width-(contentDim.width*0.25)),0,contentDim.width,contentDim.height);
      Layer whiteboxLayer = createWhitebox(map, whiteboxLoc);
      mapGraphics.add(whiteboxLayer);
     
      //title area
      Rectangle titleAreaBox = new Rectangle(whiteboxLoc.x, whiteboxLoc.y, whiteboxLoc.width, (int)(whiteboxLoc.height * 0.25));
      Layer titleAreaLayer = createTitleArea(map, titleAreaBox, monitor);
      mapGraphics.add(titleAreaLayer);
     
      return mapGraphics;
  }
 
  /**
   * Adds a whitebox mapgraphic as the top-most layer on the map.
   * On failure nothing is added to the map.
   * @param map
     * @param location represents the location the mapgraphic will sit on the map   *
   */
  private static Layer createWhitebox(Map map, Rectangle location) {
      try {

          ICatalog localCatalog = CatalogPlugin.getDefault().getLocalCatalog();

            LayerFactory layerFactory = map.getLayerFactory();
            URL resourceID = new URL(null, "mapgraphic://localhost/mapgraphic#org.locationtech.udig.tutorial.mapgraphic.Whitebox", CorePlugin.RELAXED_HANDLER); //$NON-NLS-1$
            ID id = new ID(resourceID);
            IGeoResource resource = localCatalog.getById(IGeoResource.class, id, new NullProgressMonitor());
            Layer whiteboxLayer = layerFactory.createLayer(resource);
           
            whiteboxLayer.getStyleBlackboard().put(LocationStyleContent.ID, location);
            return whiteboxLayer;
      }
      catch (Exception e) {
          throw new IllegalStateException("failed to create whitebox", e); //$NON-NLS-1$
      }
  
  }
 
  /**
     * Adds a mapgraphic as the top-most layer on the map
     * with details about the selected feature.
     * On failure nothing is added to the map.
     * @param map the map to add a new layer to
     * @param location represents the location the mapgraphic will sit on the map
     */
    protected static Layer createTitleArea(Map map, Rectangle location, IProgressMonitor monitor) {
        try {

            ICatalog localCatalog = CatalogPlugin.getDefault().getLocalCatalog();
            List<Layer> layersInternal = map.getLayersInternal();
            LayerFactory layerFactory = map.getLayerFactory();
           
            Iterator layerIterator = layersInternal.iterator();
            Layer farmParcelLayer = null;
            while (layerIterator.hasNext()) {
                Layer layer = (Layer)layerIterator.next();
                if (layer.getName().equals("SDE.DATE_PRIMARE")) {
                    farmParcelLayer = layer;
                }
            }
           
            if (farmParcelLayer == null) {
                throw new IllegalStateException("cannot find farm layer");
            }
           

            //get title from selected feature of farm layer           
            Filter farmParcelFilter = farmParcelLayer.getFilter();
            Query query = new DefaultQuery(farmParcelLayer.getSchema().getTypeName(),
                                           farmParcelFilter,
                                           new String[0]  ); //{"nume_com", "fbid"}
           
             FeatureSource<SimpleFeatureType, SimpleFeature> featureSource =
                farmParcelLayer.getResource(FeatureSource.class,
                                            new SubProgressMonitor(monitor, 1));
            FeatureCollection<SimpleFeatureType, SimpleFeature>  features = featureSource.getFeatures(query);
            FeatureIterator<SimpleFeature> featureIterator = features.features();
            if (!featureIterator.hasNext()) {
                throw new IllegalStateException("At least one feature must be selected in the farm layer. "+features.size()+ " found.");
            }
            SimpleFeature selectedFarmFeature = featureIterator.next();
            String prov = (String)selectedFarmFeature.getAttribute(0);
            String id = (String)selectedFarmFeature.getAttribute(1);
           
            //put layer info on the mapgraphic's style blackboard
            URL detailsResId = new URL(null, "mapgraphic://localhost/mapgraphic#org.locationtech.udig.tutorial.mapgraphic.TitleArea", CorePlugin.RELAXED_HANDLER);
            IGeoResource detailsRes = localCatalog.getById(IGeoResource.class, new ID(detailsResId), new NullProgressMonitor());
            
            Layer titleAreaLayer = layerFactory.createLayer(detailsRes);
            titleAreaLayer.getStyleBlackboard().put(LocationStyleContent.ID, location);
            XMLMemento selectedFeatureMemento = XMLMemento.createWriteRoot("style");
            selectedFeatureMemento.putString(FEATURE_ID_KEY, id);
            titleAreaLayer.getStyleBlackboard().put(DialogSettingsStyleContent.EXTENSION_ID, selectedFeatureMemento);
            return titleAreaLayer;
        }
        catch (Exception e) {
            throw new IllegalStateException("failed to create title area", e);
        }
       
       
    }
 
    /**
     * Adds the destination file to the catalog.
     *
     * @param destination
     * @throws MalformedURLException
     */
  private void addToCatalog(File destination) throws MalformedURLException {
    IServiceFactory serviceFactory = CatalogPlugin.getDefault().getServiceFactory();
    URL url = DataUtilities.fileToURL( destination );
        List<IService> services = serviceFactory.createService(url);
    ICatalog localCatalog = CatalogPlugin.getDefault().getLocalCatalog();;
    for (IService service : services) {
      localCatalog.add(service);
    }
  }

  /**
   * This method is responsible for creating a projection file using the WKT
   * representation of this coverage's coordinate reference system. We can
   * reuse this file in order to rebuild later the crs.
   *
   *
   * @param baseFile
   * @param coordinateReferenceSystem
   * @throws IOException
   */
  private void createProjectionFile(final String baseFile,
      final CoordinateReferenceSystem coordinateReferenceSystem)
      throws IOException {
    final File prjFile = new File(new StringBuffer(baseFile).append(".prj")
        .toString());
    BufferedWriter out = new BufferedWriter(new FileWriter(prjFile));
    out.write(coordinateReferenceSystem.toWKT());
    out.close();

  }

  /**
   * This method is responsible fro creating a world file to georeference an
   * image given the image bounding box and the image geometry. The name of
   * the file is composed by the name of the image file with a proper
   * extension, depending on the format (see WorldImageFormat). The projection
   * is in the world file.
   *
   * @param gridToWorld
   *            the transformation from the image to the world coordinates.
   * @param baseFile
   *            Basename and path for this image.
   * @throws IOException
   *             In case we cannot create the world file.
   * @throws TransformException
   * @throws TransformException
   */
  private void createWorldFile(final AffineTransform gridToWorld,
      final String baseFile) throws IOException, TransformException {
    // /////////////////////////////////////////////////////////////////////
    //
    // CRS information
    //
    // ////////////////////////////////////////////////////////////////////
    final boolean lonFirst = (XAffineTransform.getSwapXY(gridToWorld) != -1);

    // /////////////////////////////////////////////////////////////////////
    //
    // World File values
    // It is worthwhile to note that we have to keep into account the fact
    // that the axis could be swapped (LAT,lon) therefore when getting
    // xPixSize and yPixSize we need to look for it a the right place
    // inside the grid to world transform.
    //
    // ////////////////////////////////////////////////////////////////////
    final double xPixelSize = (lonFirst) ? gridToWorld.getScaleX()
        : gridToWorld.getShearY();
    final double rotation1 = (lonFirst) ? gridToWorld.getShearX()
        : gridToWorld.getScaleX();
    final double rotation2 = (lonFirst) ? gridToWorld.getShearY()
        : gridToWorld.getScaleY();
    final double yPixelSize = (lonFirst) ? gridToWorld.getScaleY()
        : gridToWorld.getShearX();
    final double xLoc = gridToWorld.getTranslateX();
    final double yLoc = gridToWorld.getTranslateY();

    // /////////////////////////////////////////////////////////////////////
    //
    // writing world file
    //
    // ////////////////////////////////////////////////////////////////////
    final StringBuffer buff = new StringBuffer(baseFile);
    buff.append(".wld");
    final File worldFile = new File(buff.toString());
    final PrintWriter out = new PrintWriter(new FileOutputStream(worldFile));
    out.println(xPixelSize);
    out.println(rotation1);
    out.println(rotation2);
    out.println(yPixelSize);
    out.println(xLoc);
    out.println(yLoc);
    out.flush();
    out.close();

  }
  private File determineDestinationFile(IMap map) {
    File exportDir = mapSelectorPage.getExportDir();
    File destination = addSuffix(new File(exportDir, map.getName()));
    if (destination.exists()) {
      boolean overwrite = !MessageDialog.openQuestion(getContainer()
          .getShell(), Messages.ExportMapToImageWizard_overwriteWarningTitle, Messages.ExportMapToImageWizard_overwriteWarningMessage
          + destination.getName());

      if (!overwrite) {
        if (!destination.delete()) {
          destination = selectFile(destination,
              Messages.ExportMapToImageWizard_unableToDeleteMsg);
        }

      } else {
        destination = selectFile(destination, Messages.ExportMapToImageWizard_selectFileTitle);
      }
    }

    if (destination == null) {
      return null;
    }

    return addSuffix(destination);
  }

  private File addSuffix(File file) {
    String path = stripEndSlash(file.getPath());

    File destination;
    String extension = imageSettingsPage.getFormat().getExtension();
    if (!path.endsWith(extension)) {
      destination = new File(path + "." + extension); //$NON-NLS-1$
    } else {
      return file;
    }
    return destination;
  }

  private String stripEndSlash(String path) {
    if (path.endsWith("/")) //$NON-NLS-1$
      return stripEndSlash(path.substring(0, path.length() - 1));
    return path;
  }

  private File selectFile(File destination, String string) {
    FileDialog dialog = new FileDialog(getShell(), SWT.SAVE);
    dialog.setText(string);
    dialog.setFilterPath(destination.getParent());
    dialog.setFileName(destination.getName());
    String file = dialog.open();
    if (file == null) {
      destination = null;
    } else {
      destination = new File(file);
    }
    return destination;
  }

  /*
   * (non-Javadoc)
   *
   * @see org.eclipse.ui.IWorkbenchWizard#init(org.eclipse.ui.IWorkbench,
   *      org.eclipse.jface.viewers.IStructuredSelection)
   */
  @SuppressWarnings("unchecked")
  public void init(IWorkbench workbench, IStructuredSelection selection) {
    mapSelectorPage.setSelection(selection);
  }

}
TOP

Related Classes of org.locationtech.udig.printing.ui.pdf.SivecoExportMapToImageWizard

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.