Skip to content

Commit

Permalink
Add DxfWriter tests
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickackermann committed Nov 14, 2024
1 parent d9d5abc commit 1af0433
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 49 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
# Resources in build output
bin/

# Test output
src/test/data/Results/

### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
Expand Down
104 changes: 85 additions & 19 deletions src/main/java/ch/geowerkstatt/lk2dxf/DxfWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,61 @@
import java.util.*;
import java.util.stream.Stream;

/**
* Writes a DXF file from INTERLIS {@link IomObject}s.
* @see <a href="https://help.autodesk.com/view/OARX/2024/ENU/?guid=GUID-235B22E0-A567-4CF6-92D3-38A2306D73F3">Autodesk DXF Reference</a>
* @see <a href="https://ezdxf.readthedocs.io/en/stable/index.html">ezdxf Documentation</a> (additional info autout DXF format)
*/
public final class DxfWriter implements AutoCloseable {
private final DecimalFormat decimalFormat;
private final Writer dxfWriter;
private int handle = 1;

public DxfWriter(String filePath) throws IOException {
this(filePath, 3, Collections.emptyList());
/**
* Creates a new DXF writer.
* @param writer The writer to write the DXF file to.
* @throws IOException If an error occurs while writing the DXF file.
*/
public DxfWriter(Writer writer) throws IOException {
this(writer, 3, Collections.emptyList(), null);
}

public DxfWriter(String filePath, int doublePrecision, Collection<LayerMapping> layerMappings) throws IOException {
/**
* Creates a new DXF writer.
* @param filePath The path to the DXF file to write.
* @param doublePrecision The number of decimal places to write for double values.
* @param layerMappings The layer mappings to use.
* @param comment The comment at the beginning of the DXF file. May be {@code null}.
* @throws IOException If an error occurs while writing the DXF file.
*/
public DxfWriter(String filePath, int doublePrecision, Collection<LayerMapping> layerMappings, String comment) throws IOException {
this(new FileWriter(filePath, StandardCharsets.UTF_8), doublePrecision, layerMappings, comment);
}

/**
* Creates a new DXF writer.
* @param writer The writer to write the DXF file to.
* @param doublePrecision The number of decimal places to write for double values.
* @param layerMappings The layer mappings to use.
* @param comment The comment at the beginning of the DXF file. May be {@code null}.
* @throws IOException If an error occurs while writing the DXF file.
*/
public DxfWriter(Writer writer, int doublePrecision, Collection<LayerMapping> layerMappings, String comment) throws IOException {
if (doublePrecision < 0) {
throw new IllegalArgumentException("doublePrecision must be positive or zero.");
}
if (layerMappings == null) {
throw new IllegalArgumentException("layerMappings must not be null.");
}

dxfWriter = new FileWriter(filePath, StandardCharsets.UTF_8);
dxfWriter = writer;
decimalFormat = new DecimalFormat("0." + "#".repeat(doublePrecision), new DecimalFormatSymbols(Locale.ROOT));

prepareDxfForWritingEntities(layerMappings);
prepareDxfForWritingEntities(layerMappings, comment);
}

private void prepareDxfForWritingEntities(Collection<LayerMapping> layerMappings) throws IOException {
var layers = new TreeMap<String, ContentWriter>();
private void prepareDxfForWritingEntities(Collection<LayerMapping> layerMappings, String comment) throws IOException {
var layers = new LinkedHashMap<String, ContentWriter>(layerMappings.size());
layers.put("0", () -> writeLayer("0", "Continuous", 0));
for (var mapping : layerMappings) {
layers.putIfAbsent(mapping.layer(), () -> writeLayer(mapping.layer(), mapping.linetype(), mapping.color()));
Expand All @@ -52,6 +82,9 @@ private void prepareDxfForWritingEntities(Collection<LayerMapping> layerMappings
.map(s -> (ContentWriter) (() -> writeBlock(s, () -> writeCircle("0", 0, 0, 0.5)))))
.toArray(ContentWriter[]::new);

if (comment != null) {
writeElement(999, comment);
}
writeHeader();
writeSection("CLASSES");

Expand All @@ -66,7 +99,7 @@ private void prepareDxfForWritingEntities(Collection<LayerMapping> layerMappings
() -> writeLineType("DashDotDot", 0.5, -0.25, 0.0, -0.25, 0.0, -0.25)),
() -> writeTable("LAYER", layers.values().toArray(ContentWriter[]::new)),
() -> writeTable("STYLE",
() -> writeStyle("cadastra", "cadastra_regular.ttf")),
layerMappings.stream().map(LayerMapping::font).filter(f -> !f.isBlank()).map(f -> (ContentWriter) () -> writeStyle(f, f)).toArray(ContentWriter[]::new)),
() -> writeTable("VIEW"),
() -> writeTable("UCS"),
() -> writeTable("APPID",
Expand All @@ -86,6 +119,10 @@ private void finishDxfAfterWritingEntities() throws IOException {
writeElement(0, "EOF");
}

/**
* Writes a LWPOLYLINE (Leightweight POLYLINE) to the DXF file. This is a 2D polyline that supports arcs, the 3rd dimension is ignored if present.
* @see <a href="https://help.autodesk.com/view/OARX/2024/ENU/?guid=GUID-748FC305-F3F2-4F74-825A-61F04D757A50">LWPOLYLINE (DXF Reference)</a>
*/
public void writeLwPolyline(String layerName, IomObject polyline) throws IOException {
var segments = polyline.getattrobj("sequence", 0);
var segmentCount = segments.getattrvaluecount("segment");
Expand All @@ -105,6 +142,10 @@ public void writeLwPolyline(String layerName, IomObject polyline) throws IOExcep
writePolylinePoints(polyline, isClosed);
}

/**
* Writes a HATCH to the DXF file. Arcs are supported, the 3rd dimension is ignored if present. The enclosed area is filled with a solid fill.
* @see <a href="https://help.autodesk.com/view/OARX/2024/ENU/?guid=GUID-C6C71CED-CE0F-4184-82A5-07AD6241F15B">HATCH (DXF Reference)</a>
*/
public void writeHatch(String layerName, IomObject multiSurface) throws IOException {
writeElement(0, "HATCH");
writeElement(5, getNextHandle());
Expand Down Expand Up @@ -189,6 +230,10 @@ private void writePolylinePoints(IomObject polyline, boolean isClosed) throws IO
}
}

/**
* Writes a CIRCLE to the DXF file.
* @see <a href="https://help.autodesk.com/view/OARX/2024/ENU/?guid=GUID-8663262B-222C-414D-B133-4A8506A27C18">CIRCLE (DXF Reference)</a>
*/
public void writeCircle(String layerName, double centerX, double centerY, double radius) throws IOException {
writeElement(0, "CIRCLE");
writeElement(5, getNextHandle());
Expand All @@ -200,6 +245,10 @@ public void writeCircle(String layerName, double centerX, double centerY, double
writeElement(40, radius);
}

/**
* Writes a INSERT to the DXF file. The predefined block symbol {@code blockName} is inserted at the specified {@code point}.
* @see <a href="https://help.autodesk.com/view/OARX/2024/ENU/?guid=GUID-28FA4CFB-9D5E-4880-9F11-36C97578252F">INSERT (DXF Reference)</a>
*/
public void writeBlockInsert(String layerName, String blockName, double rotation, IomObject point) throws IOException {
writeElement(0, "INSERT");
writeElement(5, getNextHandle());
Expand All @@ -209,25 +258,35 @@ public void writeBlockInsert(String layerName, String blockName, double rotation
writeElement(2, blockName);
writeElement(10, Double.parseDouble(point.getattrvalue("C1")));
writeElement(20, Double.parseDouble(point.getattrvalue("C2")));
writeElement(50, rotation);
writeElement(50, convertOrientation(rotation));
}

/**
* Writes a TEXT to the DXF file.
* @param layerName The layer name.
* @param textStyle The name of the STYLE entry that defines the font.
* @param text The text string.
* @param hAlignment The horizontal alignment of the text. Values from the HALIGNMENT INTERLIS enumeration.
* @param vAlignment The vertical alignment of the text. Values from the VALIGNMENT INTERLIS enumeration.
* @param orientation The rotation angle of the text.
* @param position The position of the text.
* @see <a href="https://help.autodesk.com/view/OARX/2024/ENU/?guid=GUID-62E5383D-8A14-47B4-BFC4-35824CAE8363">TEXT (DXF Reference)</a>
*/
public void writeText(String layerName, String textStyle, String text, String hAlignment, String vAlignment, double orientation, IomObject position) throws IOException {
var hAlignmentValue = switch (hAlignment) {
case "Right" -> 2;
case "Center" -> 1;
var hAlignmentValue = switch (hAlignment.toLowerCase(Locale.ROOT)) {
case "right" -> 2;
case "center" -> 1;
default -> 0; // Left
};
var vAlignmentValue = switch (vAlignment) {
case "Top", "Cap" -> 3;
case "Half" -> 2;
case "Bottom" -> 1;
var vAlignmentValue = switch (vAlignment.toLowerCase(Locale.ROOT)) {
case "top", "cap" -> 3;
case "half" -> 2;
case "bottom" -> 1;
default -> 0; // Base
};
var isDefaultAlignment = hAlignmentValue == 0 && vAlignmentValue == 0;

// Convert transfer orientation to DXF orientation
orientation = (-orientation + 90 + 360) % 360;
orientation = convertOrientation(orientation);

writeElement(0, "TEXT");
writeElement(5, getNextHandle());
Expand Down Expand Up @@ -338,7 +397,7 @@ private void writeBlock(String name, ContentWriter... writeContent) throws IOExc
writeElement(100, "AcDbBlockEnd");
}

public void writeMinimalDictionary() throws IOException {
private void writeMinimalDictionary() throws IOException {
var rootHandle = getNextHandle();
var entryHandle = getNextHandle();

Expand Down Expand Up @@ -424,6 +483,13 @@ private String getNextHandle() {
return Integer.toHexString(handle++).toUpperCase(Locale.ROOT);
}

/**
* Convert transfer orientation to DXF orientation.
*/
private double convertOrientation(double angle) {
return (-angle + 90 + 360) % 360;
}

@Override
public void close() throws Exception {
finishDxfAfterWritingEntities();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/ch/geowerkstatt/lk2dxf/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private static void processFiles(LK2DxfOptions options) {
Optional<Geometry> perimeter = options.parsePerimeter();
AtomicInteger counter = new AtomicInteger();

try (var dxfWriter = new DxfWriter(options.dxfFile(), 3, ObjectMapper.getLayerMappings())) {
try (var dxfWriter = new DxfWriter(options.dxfFile(), 3, ObjectMapper.getLayerMappings(), "lk2dxf " + Main.VERSION)) {
for (String xtfFile : options.xtfFiles()) {
try (LKMapXtfReader reader = new LKMapXtfReader(new File(xtfFile))) {
ObjectMapper mapper = new ObjectMapper();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public final class ObjectMapper {
}
}

/**
* Get the layer mappings as an immutable list.
*/
public static List<LayerMapping> getLayerMappings() {
return Collections.unmodifiableList(LAYER_MAPPINGS);
}
Expand Down
Loading

0 comments on commit 1af0433

Please sign in to comment.