PDF'e aktarılan 1:100 ölçekli bir kat planı Acrobat'ta açılır ve mesafe aracı, 5 m güney duvarını kağıttaki uzunluğu olan 1.97 in olarak raporlar. Eksik olan şey, point'leri metreye çeviren rectilinear (/RL) measure sözlüğüne sahip bir /VP viewport'tur — HotPDF bunu Delphi tarafında yazar; haritalar için geospatial (/GEO) kontrol noktalarıyla birlikte
Viewport'un ölçüm aracına verdiği şey
Viewport, sayfa üzerinde kendi ölçüm koordinat sistemini getiren bir dikdörtgendir. Page dictionary'nin isteğe bağlı /VP girdisi (ISO 32000-1 Table 30, PDF 1.6), /Type /Viewport sözlüklerinden oluşan bir dizi tutar ve her biri yalnızca bir /BBox'a ihtiyaç duyar: /Name onu viewer arayüzünde etiketler, /Measure ölçeği verir (ISO 32000-1 §12.9, Table 260; aynı sözlük ISO 32000-2'de Table 265'tir). Hiç viewport'u olmayan bir sayfa, Acrobat'ın ölçüm araçlarını 1:1 kağıt ölçeğinde bırakır; 1.97 in, sadakatle ölçülmüş 50 mm mürekkepten ibarettir. /BBox, default user space'te yazılır; önce sol alt köşe, sonra sağ üst. O köşe sırası, ölçüm x ve y eksenlerinin yönünü belirler ve standart bu eksenleri sayfanın /Rotate'inden bağımsız ilan eder. Sayfanın kutuları boyunca bir kez bile /Rotate'i düzleştirecek olursanız, her /VP dikdörtgeni içeriği izlemek zorunda olan bir geometri parçası daha olur
Viewport'lar üst üste binebilir ve §12.9 bu çakışmayı dizi sırasıyla çözer: bir noktanın altındaki viewport'u arayan okuyucu son /VP girdisinden başlar, geriye doğru yürür ve /BBox'ı o noktayı içeren girdide durur. Bu yüzden 1:100 bir palet üzerindeki 1:20 merdiven detayı, paleti kaplayan viewport'tan sonra gelmek zorundadır — onu daha önce eklerseniz büyük dikdörtgen her hit testini kazanır ve detay beş kat fazla uzun ölçülür. Mesafeler başlangıç noktalarını izler. Detayın içinde başlayıp ana plan üzerinde biten bir çizginin tamamı, sınırın içinden mi geçtiğine bakılmaksızın 1:20 üzerinden ölçülür
Rectilinear ölçeğin arkasındaki aritmetik
Rectilinear measure sözlüğü (ISO 32000-1 Table 262, ISO 32000-2'de Table 267) dört zorunlu girdi taşır ve aritmetiğinin tamamı /C'ye asılıdır. /X, /D ve /A number format dizileridir: büyükten küçüğe dizilmiş number format sözlükleri (Table 263), her biri bir /U etiketi ve bir /C çevrim çarpanıyla. Dizideki sonraki sözlükler, kendilerinden önceki birimin üzerinden çevrim yapar; baştaki sözlük ise dizinin hangi girdiye ait olduğuna göre farklı bir yerden başlar:
| Girdi | Zorunlu | Baştaki /C'nin çevirdiği kaynak | 1:100 metrik palet |
|---|---|---|---|
| /R | Evet | Hiçbir şey; insan tarafından okunabilen oran metni | (1 cm = 1 m) |
| /X | Evet | Default user space birimleri | /U (m) /C 0.0352778 |
| /Y | y birimleri ya da çarpanlar farklılaştığında | Default user space birimleri | Yok |
| /D | Evet | Baştaki /X birimi | /U (m) /C 1 |
| /A | Evet | Baştaki /X biriminin karesi | /U (sq m) /C 1 |
| /T | Hayır | Derece | Yok |
| /S | Hayır | Baştaki /Y biriminin baştaki /X birimine bölümü | Yok |
Metrik bir 1:100 planda ölçek tek bir sayıda yaşar. Bir point, 25.4 / 72 = 0.352778 mm kağıttır ve 35.2778 mm binayı temsil eder; bu yüzden /X, /U (m) ve /C 0.0352778 alır. /D metreden, /A metrekareden başlar ve ikisi de /C 1 alır. /X çarpanını /D'ye kopyalayıp /A için karesini almak, ölçeği iki kez uygular: 5 m duvar 0.18 m olarak geri döner ve 20 m² bir oda 0.025 m²'ye küçülür. Bu baştaki /C 1'i, gereksiz göründüğü yerlerde bile her viewport'ta /D'ye ve /A'ya yazın. Table 262 her iki diziyi de zorunlu kılar ve bir yedek tanımlamaz; eksik ya da uydurulmuş bir çarpana rastlayan viewer doğaçlama yapar ve iki viewer iki farklı biçimde doğaçlayabilir. Eksiksiz yazılmış bir sözlük, her okumayı dosyanın kendisine sabitler ve birimler, gözden geçirenin title block ile karşılaştırabileceği kodda durur. Number format modelinin geri kalanı şöyle çalışır:
- Zincirlenmiş birimler önceki-birim kuralını izler; standart kendi örneğinde de böyle yapar: /C 1 ile mil, /C 5280 ile feet ve /C 12 ile inch, /F /F /D 8 ile de sekizde birlere yuvarlanarak çıktıdaki "1 mi 2,378 ft 7 5/8 in" üretir
- /R insanlar için bir başlıktır; doğru bir /C üzerine yazılmış yanlış bir oran string'i bile doğru sayıları verir
- Dikey ölçeği abartılı profiller — Table 262'deki "in X 1 cm = 1 m, in Y 1 cm = 30 m" durumu — /Y'ye ek olarak y birimlerini x birimlerine çeviren bir /CYX çarpanı ister; /CYX yoksa okuyucu mesafe, alan ve açı hesaplamayı reddedebilir
1:20 detay içeren bir 1:100 plan yazmak
THPDFPage.AddRectilinearViewportEx, viewport dikdörtgenini ve alanları /R, /X, /Y, /D, /A, /T, /S, /O ve /CYX'e karşılık düşen bir THPDFRectilinearMeasureInfo kaydı alır; her number format dizisi THPDFNumberFormatEntry kayıtları halinde tutulur. Yukarıdaki tablo değişmeden içine girer ve okuyucuya çıkarım yapacak hiçbir şey bırakmaz. Aşağıdaki kod çalışmadan önce geçerli olan çağrı kuralları:
- Bir rectilinear viewport PDF 1.6 ister. HotPDF,
THotPDF.Versiondeğerini gerektiğindepdf16'ya yükseltir;StrictVersionLocketkinse metot hiçbir şey yazmaz ve -1 döndürür - Boş bir /R ya da girdisiz bir /X, /D, /A dizisi istisna fırlatır; Table 262 dördünü de zorunlu kılar
- Her çağrı /VP'ye ekler; bu yüzden plan detaydan önce girer ve detay, okuyucunun ötekilerden önce sınadığı sıraya yerleşir
uses
SysUtils, HPDFDoc;
function NumberFormat(const UnitLabel: AnsiString; Factor: Double;
Precision: Integer): THPDFNumberFormatEntry;
begin
Result := Default(THPDFNumberFormatEntry);
Result.UnitLabel := UnitLabel; // /U
Result.ConversionFactor := Factor; // /C
Result.FractionStyle := mnfDecimal; // /F /D
Result.Denominator := Precision; // 100 = yüzde birler
end;
// 1:Scale'de metrik çizim; tüm okumalar metre cinsinden
function MetricScale(const Ratio: AnsiString;
Scale: Double): THPDFRectilinearMeasureInfo;
const
MetersPerPoint = 0.0254 / 72; // bir birimin kağıt üzerindeki uzunluğu
begin
Result := Default(THPDFRectilinearMeasureInfo);
Result.ScaleRatio := Ratio; // /R
SetLength(Result.XFormats, 1);
Result.XFormats[0] := NumberFormat('m', MetersPerPoint * Scale, 1000);
SetLength(Result.DistanceFormats, 1);
Result.DistanceFormats[0] := NumberFormat('m', 1, 100); // X birimlerinden
SetLength(Result.AreaFormats, 1);
Result.AreaFormats[0] := NumberFormat('sq m', 1, 100); // X birimlerinin karesinden
end;
procedure WritePlanSheet(const FileName: string);
var
Pdf: THotPDF;
Plan, Detail: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.AutoLaunch := False;
Pdf.FileName := FileName;
Pdf.BeginDoc;
// ... kat planını ve merdiven detayını çizin ...
// Dikdörtgenler PDF default user space'tedir: point cinsinden, orijin sol altta
Plan := Pdf.CurrentPage.AddRectilinearViewportEx('Ground floor 1:100',
36, 36, 576, 756, MetricScale('1 cm = 1 m', 100));
Detail := Pdf.CurrentPage.AddRectilinearViewportEx('Stair detail 1:20',
396, 72, 576, 252, MetricScale('1 cm = 20 cm', 20));
if (Plan < 0) or (Detail < 0) then
raise Exception.Create('StrictVersionLock keeps the file below PDF 1.6');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Dört dikdörtgen argümanı olduğu gibi /BBox'a gider; PDF default user space'indedirler, yani point cinsinden ve sol alt köşeden ölçülür. /X çarpanının çevirdiği uzay tam da budur; dikdörtgen ile ölçek aynı birimi paylaşır. HotPDF çizim çağrıları ise başka bir uzay kullanır: orijin sol üsttedir ve koordinatlar 1/Resolution inch cinsindendir. HotPDF çizim birimleri ile PDF point'leri arasındaki fark burada tam gücüyle geçerlidir — sol üst koordinatlarıyla çizilen bir detayın y değerleri sayfa yüksekliğine karşı çevrilip point'e dönüştürülmelidir, yoksa ölçüm dikdörtgeni paletin öbür yarısına düşer. Büyük format sayfalar bir çarpan daha ekler. THPDFPage.SetUserUnit ile /UserUnit atandığında bir default birim, UserUnit / 72 inch'tir; dolayısıyla /X çarpanı aynı değerle ölçeklenir
GPTS koordinatları hangi sırayı kullanır?
Bir harita viewport'u, /Subtype /GEO taşıyan bir measure sözlüğü getirir; bu, PDF 1.7'ye bir Adobe uzantısı olarak başladı ve PDF 2.0'da standart oldu (ISO 32000-2 §12.10.2, Table 269). /GCS zorunludur ve bir /Type /GEOGCS sözlüğü (Table 270) ya da bir /Type /PROJCS sözlüğü (Table 271) tutar; ikisi de bir /EPSG kodu ya da bir /WKT string'iyle tanımlanır. Coğrafi bir sistem için standart ikiden birine izin verir ve ikisinin bir arada bulunmasını yasaklar. Koordinat sisteminin bir kodu varsa her zaman EPSG kullanın. Bir integer, string kaçış dizileriyle bozulamaz ve okuyucular onu, GIS araçlarının kullandığı aynı kayıt defterine göre çözer; WKT ise kodsuz kalan özel projeksyonlar için kalır. /DCS isteğe bağlı olarak konum gösterimi için başka bir sistem adlandırır. /PDU, tercih edilen gösterim birimlerini adlarıyla listeler — KM, SQKM ve DEG gibi lineer, alan ve açısal birimler — ve HotPDF bunu, THPDFGeoMeasureInfo içindeki üç birim de doldurulduğunda yazar
/GPTS çiftleri, /GCS coğrafiyken önce enlem sonra boylam; projekteyken doğu-kuzey (easting-northing) sırasındadır. Sıra değişir. Doğrudan /GPTS'e beslenen GIS x/y çiftleri bir UTM haritası için doğru, WGS 84 için yanlış çıkar. EPSG:4326 kendisi enlemi boylamın önüne koyar; GeoJSON ve çoğu web harita API'si tersini yapar ve web harita sırasıyla kaydedilmiş bir Zürih sokak haritası sonucu gösterir: 8.54° doğu bir enlem olarak, 47.37° kuzey bir boylam olarak okunur ve imleç, Etiyopya-Somali sınırı yakınlarındaki konumları raporlar. /LPTS, eşleşen noktaları viewport'un birim karesinde tutar: (0, 0) /BBox'ın sol alt köşesi, (1, 1) sağ üst köşesidir ve her /GPTS çiftine bir çift düşer; oraya yapıştırılan sayfa koordinatları kontrol noktalarını haritadan yüzlerce kare uzağa gönderir. Georeference'ın geçerli olduğu yeri sınırlayan neatline olan /Bounds da aynı birim kareyi kullanır ve [0 0 0 1 1 1 1 0] değerine varsayılanlaşır. HotPDF, bir şey yazmadan önce şekli denetler: /GPTS, en az dört sayılı çift sayıda değer ister ve boş olmayan bir /LPTS uzunluğunu eşlemek zorundadır
Bir harita viewport'una nokta bulutu eklemek
Bir GEO viewport'u bir nokta veri sözlüğü de taşıyabilir (ISO 32000-2 §12.10.5, Table 272): /Type /PtData, /Subtype /Cloud, sütun başlıkları gibi işleyen bir /Names dizisi ve satırlardan oluşan bir /XPTS dizisi. Derece cinsinden LAT ve LON ile metre cinsinden ALT önceden tanımlı başlıklardır ve standart satırları sırasız bir koleksiyon sayar. Yapısal olarak /XPTS iç içe geçerken /GPTS düz kalır: nokta başına bir iç dizi, /Names'e göre boyutlanır; yani [[47.3769 8.5417 408] [47.3780 8.5400 410]]. THPDFPage.SetViewportPointDataCloud yine de tek bir düz liste kabul eder — bir CSV çıktısının ürettiği şekil — ve sütun sayısına göre satırlara böler; nokta ondalık ayracıyla sayı olarak parse edilen bir değer PDF sayısına dönüşür, ötekiler string olarak kalır ve sütun sayısının katı olmayan bir sayı istisna fırlatır. Her iki çağrı da PDF 2.0 ister ve bulut, AddGeospatialViewport'un döndürdüğü viewport indeksine eklenir
procedure AddCityMap(Page: THPDFPage);
const
// Neatline köşeleri, enlem boylamdan önce: NW, NE, SE, SW
CornerLatLon: array[0..7] of Double =
(47.40, 8.45, 47.40, 8.62, 47.33, 8.62, 47.33, 8.45);
// Aynı köşeler viewport birim karesinde
CornerUnit: array[0..7] of Double =
(0, 1, 1, 1, 1, 0, 0, 0);
var
Geo: THPDFGeoMeasureInfo;
Map, I: Integer;
begin
Geo := Default(THPDFGeoMeasureInfo);
Geo.GCS.CSType := cstGEOGCS;
Geo.GCS.EPSG := 4326; // WGS 84; WKT boş kalır
Geo.LinearUnit := 'KM';
Geo.AreaUnit := 'SQKM';
Geo.AngularUnit := 'DEG';
SetLength(Geo.GPTS, Length(CornerLatLon));
SetLength(Geo.LPTS, Length(CornerUnit));
for I := 0 to High(CornerLatLon) do
begin
Geo.GPTS[I] := CornerLatLon[I];
Geo.LPTS[I] := CornerUnit[I];
end;
// 540 x 324 pt, haritanın 12.8 km x 7.8 km oranını kabaca korur
Map := Page.AddGeospatialViewport('City center', 36, 252, 576, 576, Geo);
if Map < 0 then
raise Exception.Create('GEO viewports need PDF 2.0');
// Üç sütun; her üç değer bir /XPTS satırı olur
Page.SetViewportPointDataCloud(Map, ['LAT', 'LON', 'ALT'],
['47.3769', '8.5417', '408',
'47.3780', '8.5400', '410']);
end;
Viewport'ları geri okumak ve sunucuda ölçmek
THotPDF.GetLoadedPageViewports, sıfır tabanlı bir sayfa indeksi için bir THPDFViewportInfoArray döndürür: dosya sırasına göre her /VP girdisi için bir THPDFViewportInfo; BBox, Name, MeasureSubtype, parse edilmiş Rectilinear ya da Geospatial kaydı ve varsa PtData satırlarıyla. /Subtype'ı atlayan bir measure sözlüğü, ISO 32000-1 Table 261'in atadığı varsayılan olan RL olarak geri gelir. Aşağıdaki iki fonksiyon, bir viewer'ın bir tıklamayla yaptığını yeniden üretir: ters yürüyüşle viewport'u bulmak, sonra sayfa üzerindeki bir mesafeyi /X (ya da /CYX'li /Y) ve baştaki /D çarpanı üzerinden gerçek birimlere çevirmek. Sınır testi Min ile Max kullanır; çünkü sayfaya göre döndürülmüş bir ölçüm çerçevesi köşelerini başka bir sırayla listeleyebilir
uses
SysUtils, Math, HPDFDoc;
// /VP'yi son girdiden geriye doğru gez; noktayı içeren BBox'a en erken ulaşan kazanır
function ViewportAt(const VPs: THPDFViewportInfoArray; X, Y: Double): Integer;
var
I: Integer;
begin
for I := High(VPs) downto 0 do
if (X >= Min(VPs[I].BBoxLeft, VPs[I].BBoxRight)) and
(X <= Max(VPs[I].BBoxLeft, VPs[I].BBoxRight)) and
(Y >= Min(VPs[I].BBoxBottom, VPs[I].BBoxTop)) and
(Y <= Max(VPs[I].BBoxBottom, VPs[I].BBoxTop)) then
Exit(I);
Result := -1;
end;
// İki sayfa noktası arasındaki mesafe, baştaki /D birimi cinsinden
function MeasuredDistance(const VP: THPDFViewportInfo;
X1, Y1, X2, Y2: Double): Double;
var
RL: THPDFRectilinearMeasureInfo;
DX, DY: Double;
begin
RL := VP.Rectilinear;
if (Length(RL.XFormats) = 0) or (Length(RL.DistanceFormats) = 0) then
raise Exception.Create('Viewport has no usable /X or /D array');
DX := (X2 - X1) * RL.XFormats[0].ConversionFactor;
if Length(RL.YFormats) > 0 then
begin
if not RL.HasCYX then
raise Exception.Create('/Y without /CYX: distance is undefined');
DY := (Y2 - Y1) * RL.YFormats[0].ConversionFactor * RL.CYX;
end
else
DY := (Y2 - Y1) * RL.XFormats[0].ConversionFactor;
Result := Sqrt(Sqr(DX) + Sqr(DY)) * RL.DistanceFormats[0].ConversionFactor;
end;
procedure MeasureStairRun;
var
Pdf: THotPDF;
VPs: THPDFViewportInfoArray;
Hit: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('floor-plan.pdf') > 0 then
begin
VPs := Pdf.GetLoadedPageViewports(0);
Hit := ViewportAt(VPs, 450, 150); // kararı başlangıç noktası verir
if (Hit >= 0) and (VPs[Hit].MeasureSubtype = 'RL') then
Writeln(Format('%s (%s): %.2f m', [string(VPs[Hit].Name),
string(VPs[Hit].Rectilinear.ScaleRatio),
MeasuredDistance(VPs[Hit], 450, 150, 450, 235)]));
end;
finally
Pdf.Free;
end;
end;
Daha önce yazılan palet için beklenen çıktı: Stair detail 1:20 (1 cm = 20 cm): 0.60 m; çünkü point başına 0.0070556 m ile 85 point, 0.5997 m eder. İki AddRectilinearViewportEx çağrısının yerini değiştirin ve aynı 85 point'lik çizgi plan viewport'una düşer, 3.00 m okur. Üretmediğiniz taranmış bir çizim seti için THotPDF.AddPageRectilinearViewportEx, AddPageGeospatialViewport ve SetPageViewportPointDataCloud, aynı işi yüklenmiş bir belge üzerinde SaveLoadedDocument öncesinde sayfa indeksiyle yapar. Çizgi, polyline ve polygon annotation'ları kendi /Measure'lerini taşıyabilir (ISO 32000-1 Tables 175 ve 178) ve GetLoadedAnnotationMeasure bunu aynı RL ve GEO kayıtlarına parse eder; bu, gözden geçirilmiş bir PDF'teki ölçüm annotation'ları başka bir araçtan geldiğinde işe yarar
/D ve /A, 1'e sabitlendiğinde 1:100 ölçeğini taşıyan tek sayı, 0.0352778'lik /X çarpanıdır ve §12.9'u izleyen herhangi bir okuyucuda 5 m güney duvarı 5.00 m okumalıdır. Aynı kayıt türü GetLoadedPageViewports'tan da geri gelir; böylece HotPDF Delphi PDF component ile yazılmış bir dosya yeniden yüklenebilir ve çarpanları, kodunuzun içine yazdığı değerlerle karşılaştırılabilir